/*-------------------------------------------------------------------------
 *
 * indxpath.c
 *	  确定哪些索引可用于扫描给定关系的例程，并据此创建路径。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/path/indxpath.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <math.h>

#include "access/stratnum.h"
#include "access/sysattr.h"
#include "catalog/pg_am.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "utils/lsyscache.h"
#include "utils/selfuncs.h"


/* XXX 查看 PartCollMatchesExprColl */
#define IndexCollMatchesExprColl(idxcollation, exprcollation) \
	((idxcollation) == InvalidOid || (idxcollation) == (exprcollation))

/* 我们是否在寻找普通的索引扫描、位图扫描或二者 */
typedef enum
{
	ST_INDEXSCAN,				/* 必须支持 amgettuple */
	ST_BITMAPSCAN,				/* 必须支持 amgetbitmap */
	ST_ANYSCAN					/* 二者都可以 */
} ScanTypeControl;

/* 用于收集与索引匹配的条件子句的数据结构 */
typedef struct
{
	bool		nonempty;		/* 如果列表不全为空则为真 */
	/* 索引子句节点的列表，每个索引列一个列表 */
	List	   *indexclauses[INDEX_MAX_KEYS];
} IndexClauseSet;

/* Per-path data used within choose_bitmap_and() */
typedef struct
{
	Path	   *path;			/* IndexPath, BitmapAndPath, or BitmapOrPath */
	List	   *quals;			/* 它使用的WHERE子句 */
	List	   *preds;			/* 它的部分索引的谓词 */
	Bitmapset  *clauseids;		/* quals + preds 以 bitmapset 表示 */
	bool		unclassifiable; /* 处理的 quals + preds 太多？ */
} PathClauseUsage;

/* ec_member_matches_indexcol 的回调参数 */
typedef struct
{
	IndexOptInfo *index;		/* 我们正在考虑的索引 */
	int			indexcol;		/* 我们想要匹配的索引列 */
} ec_member_matches_arg;


static void fc_consider_index_join_clauses(PlannerInfo *fc_root, RelOptInfo *fc_rel,
										IndexOptInfo *fc_index,
										IndexClauseSet *fc_rclauseset,
										IndexClauseSet *fc_jclauseset,
										IndexClauseSet *fc_eclauseset,
										List **fc_bitindexpaths);
static void fc_consider_index_join_outer_rels(PlannerInfo *fc_root, RelOptInfo *fc_rel,
										   IndexOptInfo *fc_index,
										   IndexClauseSet *fc_rclauseset,
										   IndexClauseSet *fc_jclauseset,
										   IndexClauseSet *fc_eclauseset,
										   List **fc_bitindexpaths,
										   List *fc_indexjoinclauses,
										   int fc_considered_clauses,
										   List **fc_considered_relids);
static void get_join_index_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel,
								 IndexOptInfo *fc_index,
								 IndexClauseSet *fc_rclauseset,
								 IndexClauseSet *fc_jclauseset,
								 IndexClauseSet *fc_eclauseset,
								 List **fc_bitindexpaths,
								 Relids fc_relids,
								 List **fc_considered_relids);
static bool fc_eclass_already_used(EquivalenceClass *fc_parent_ec, Relids fc_oldrelids,
								List *fc_indexjoinclauses);
static bool fc_bms_equal_any(Relids fc_relids, List *fc_relids_list);
static void get_index_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel,
							IndexOptInfo *fc_index, IndexClauseSet *fc_clauses,
							List **fc_bitindexpaths);
static List *fc_build_index_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel,
							   IndexOptInfo *fc_index, IndexClauseSet *fc_clauses,
							   bool fc_useful_predicate,
							   ScanTypeControl fc_scantype,
							   bool *fc_skip_nonnative_saop,
							   bool *fc_skip_lower_saop);
static List *fc_build_paths_for_OR(PlannerInfo *fc_root, RelOptInfo *fc_rel,
								List *fc_clauses, List *fc_other_clauses);
static List *fc_generate_bitmap_or_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel,
									  List *fc_clauses, List *fc_other_clauses);
static Path *fc_choose_bitmap_and(PlannerInfo *fc_root, RelOptInfo *fc_rel,
							   List *fc_paths);
static int	fc_path_usage_comparator(const void *fc_a, const void *fc_b);
static Cost fc_bitmap_scan_cost_est(PlannerInfo *fc_root, RelOptInfo *fc_rel,
								 Path *fc_ipath);
static Cost fc_bitmap_and_cost_est(PlannerInfo *fc_root, RelOptInfo *fc_rel,
								List *fc_paths);
static PathClauseUsage *fc_classify_index_clause_usage(Path *fc_path,
													List **fc_clauselist);
static void fc_find_indexpath_quals(Path *fc_bitmapqual, List **fc_quals, List **fc_preds);
static int	fc_find_list_position(Node *fc_node, List **fc_nodelist);
static bool fc_check_index_only(RelOptInfo *fc_rel, IndexOptInfo *fc_index);
static double get_loop_count(PlannerInfo *fc_root, Index fc_cur_relid, Relids fc_outer_relids);
static double fc_adjust_rowcount_for_semijoins(PlannerInfo *fc_root,
											Index fc_cur_relid,
											Index fc_outer_relid,
											double fc_rowcount);
static double fc_approximate_joinrel_size(PlannerInfo *fc_root, Relids fc_relids);
static void fc_match_restriction_clauses_to_index(PlannerInfo *fc_root,
											   IndexOptInfo *fc_index,
											   IndexClauseSet *fc_clauseset);
static void fc_match_join_clauses_to_index(PlannerInfo *fc_root,
										RelOptInfo *fc_rel, IndexOptInfo *fc_index,
										IndexClauseSet *fc_clauseset,
										List **fc_joinorclauses);
static void fc_match_eclass_clauses_to_index(PlannerInfo *fc_root,
										  IndexOptInfo *fc_index,
										  IndexClauseSet *fc_clauseset);
static void fc_match_clauses_to_index(PlannerInfo *fc_root,
								   List *fc_clauses,
								   IndexOptInfo *fc_index,
								   IndexClauseSet *fc_clauseset);
static void fc_match_clause_to_index(PlannerInfo *fc_root,
								  RestrictInfo *fc_rinfo,
								  IndexOptInfo *fc_index,
								  IndexClauseSet *fc_clauseset);
static IndexClause *fc_match_clause_to_indexcol(PlannerInfo *fc_root,
											 RestrictInfo *fc_rinfo,
											 int fc_indexcol,
											 IndexOptInfo *fc_index);
static IndexClause *fc_match_boolean_index_clause(PlannerInfo *fc_root,
											   RestrictInfo *fc_rinfo,
											   int fc_indexcol, IndexOptInfo *fc_index);
static IndexClause *fc_match_opclause_to_indexcol(PlannerInfo *fc_root,
											   RestrictInfo *fc_rinfo,
											   int fc_indexcol,
											   IndexOptInfo *fc_index);
static IndexClause *fc_match_funcclause_to_indexcol(PlannerInfo *fc_root,
												 RestrictInfo *fc_rinfo,
												 int fc_indexcol,
												 IndexOptInfo *fc_index);
static IndexClause *get_index_clause_from_support(PlannerInfo *fc_root,
												  RestrictInfo *fc_rinfo,
												  Oid fc_funcid,
												  int fc_indexarg,
												  int fc_indexcol,
												  IndexOptInfo *fc_index);
static IndexClause *fc_match_saopclause_to_indexcol(PlannerInfo *fc_root,
												 RestrictInfo *fc_rinfo,
												 int fc_indexcol,
												 IndexOptInfo *fc_index);
static IndexClause *fc_match_rowcompare_to_indexcol(PlannerInfo *fc_root,
												 RestrictInfo *fc_rinfo,
												 int fc_indexcol,
												 IndexOptInfo *fc_index);
static IndexClause *fc_expand_indexqual_rowcompare(PlannerInfo *fc_root,
												RestrictInfo *fc_rinfo,
												int fc_indexcol,
												IndexOptInfo *fc_index,
												Oid fc_expr_op,
												bool fc_var_on_left);
static void fc_match_pathkeys_to_index(IndexOptInfo *fc_index, List *fc_pathkeys,
									List **fc_orderby_clauses_p,
									List **fc_clause_columns_p);
static Expr *fc_match_clause_to_ordering_op(IndexOptInfo *fc_index,
										 int fc_indexcol, Expr *fc_clause, Oid fc_pk_opfamily);
static bool fc_ec_member_matches_indexcol(PlannerInfo *fc_root, RelOptInfo *fc_rel,
									   EquivalenceClass *fc_ec, EquivalenceMember *fc_em,
									   void *fc_arg);


/*
 * create_index_paths()
 *	  为给定关系生成所有有趣的索引路径。
 *	  候选路径被添加到关系的 pathlist（使用 add_path）。
 *
 * 要考虑进行索引扫描，索引必须匹配查询的 qual 条件中的一个或多个
 * 限制子句或连接子句，或者匹配查询的 ORDER BY 条件，或者具有与查询的
 * qual 条件匹配的谓词。
 *
 * 索引扫描有两种基本类型。 "普通"索引扫描仅使用
 * 限制子句（可能根本不使用）作为其 indexqual，
 * 因此可以在任何上下文中应用。 "参数化"索引扫描使用
 * 连接子句（加限制子句，如果可用）作为其 indexqual。
 * 将这种扫描与提供在其 indexqual 中使用的其他
 * 变量的关系之一连接时，参数化扫描必须作为
 * 嵌套循环连接的内层关系出现；它不能在外层使用，
 * 也不能在合并或哈希连接中使用。在这种情况下，其他关系的
 * 属性的值在索引路径的任何一次扫描中都是可用和固定的。
 *
 * 为当前查询生成的每个普通或
 * 参数化索引扫描，这个例程会生成一个 IndexPath 并提交给 add_path()。
 *
 * 'rel' 是我们希望生成索引路径的关系
 *
 * 注意：必须事先对该关系运行 check_index_predicates()。
 *
 * 注意：在涉及关系的 tlist 中的 LATERAL 引用的情况下，
 * rel->lateral_relids 可能是非空的。目前，我们将
 * lateral_relids 包含在报告的每个路径的参数化中，但在其他方面
 * 不考虑。任何这样的关系 *必须* 作为参数源可用的事实
 * 可能应该影响我们选择索引资格的方式……但目前来看，这
 * 似乎不值得对此烦恼。特别是关于“未参数化”的路径的以下注释
 * 应理解为“就 indexquals 而言是未参数化的”。
 */
void create_index_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel)
{
	List	   *fc_indexpaths;
	List	   *fc_bitindexpaths;
	List	   *fc_bitjoinpaths;
	List	   *fc_joinorclauses;
	IndexClauseSet fc_rclauseset;
	IndexClauseSet fc_jclauseset;
	IndexClauseSet fc_eclauseset;
	ListCell   *fc_lc;

	/* 如果没有索引，跳过整个麻烦 */
	if (fc_rel->indexlist == NIL)
		return;

	/* Bitmap 路径被收集，然后在最后处理 */
	fc_bitindexpaths = fc_bitjoinpaths = fc_joinorclauses = NIL;

	/* 依次检查每个索引 */
	foreach(fc_lc, fc_rel->indexlist)
	{
		IndexOptInfo *fc_index = (IndexOptInfo *) lfirst(fc_lc);

		/* 保护 IndexClauseSets 中的有限大小数组 */
		Assert(fc_index->nkeycolumns <= INDEX_MAX_KEYS);

		/*
		 * 忽略不匹配查询的部分索引。
		 * （generate_bitmap_or_paths() 可能能够对
		 * 它们做一些事情，但这里不关心。）
		 */
		if (fc_index->indpred != NIL && !fc_index->predOK)
			continue;

		/*
		 * 确定可以匹配该索引的限制子句。
		 */
		MemSet(&fc_rclauseset, 0, sizeof(fc_rclauseset));
		fc_match_restriction_clauses_to_index(fc_root, fc_index, &fc_rclauseset);

		/*
		 * 根据限制子句构建索引路径。这些将是
		 * 非参数化路径。普通路径直接调用 add_path()，
		 * bitmap 路径被添加到 bitindexpaths 以便在下面处理。
		 */
		get_index_paths(fc_root, fc_rel, fc_index, &fc_rclauseset,
						&fc_bitindexpaths);

		/*
		 * 确定可以匹配该索引的连接子句。暂时我们将它们
		 * 与限制子句分开。请注意，这一步只找到
		 * 还没有被合并到 EquivalenceClasses 的“松散”连接子句。
		 * 此外，收集连接 OR 子句以备后用。
		 */
		MemSet(&fc_jclauseset, 0, sizeof(fc_jclauseset));
		fc_match_join_clauses_to_index(fc_root, fc_rel, fc_index,
									&fc_jclauseset, &fc_joinorclauses);

		/*
		 * 寻找可以生成与索引匹配的连接子句的 EquivalenceClasses。
		 */
		MemSet(&fc_eclauseset, 0, sizeof(fc_eclauseset));
		fc_match_eclass_clauses_to_index(fc_root, fc_index,
									  &fc_eclauseset);

		/*
		 * 如果我们找到了任何普通的或 eclass 连接子句，使用它们
		 * 构建参数化索引路径。
		 */
		if (fc_jclauseset.nonempty || fc_eclauseset.nonempty)
			fc_consider_index_join_clauses(fc_root, fc_rel, fc_index,
										&fc_rclauseset,
										&fc_jclauseset,
										&fc_eclauseset,
										&fc_bitjoinpaths);
	}

	/*
	 * 为限制列表中存在的任何适当的 OR 子句生成 BitmapOrPaths。
	 * 将这些添加到 bitindexpaths。
	 */
	fc_indexpaths = fc_generate_bitmap_or_paths(fc_root, fc_rel,
										  fc_rel->baserestrictinfo, NIL);
	fc_bitindexpaths = list_concat(fc_bitindexpaths, fc_indexpaths);

	/*
	 * 同样，为连接子句列表中存在的任何适当的 OR 子句生成 BitmapOrPaths。
	 * 将这些添加到 bitjoinpaths。
	 */
	fc_indexpaths = fc_generate_bitmap_or_paths(fc_root, fc_rel,
										  fc_joinorclauses, fc_rel->baserestrictinfo);
	fc_bitjoinpaths = list_concat(fc_bitjoinpaths, fc_indexpaths);

	/*
	 * 如果我们找到了任何可用的内容，为最有前途的限制位图
	 * 索引路径组合生成 BitmapHeapPath。请注意，无论有多少
	 * 索引存在，这里只有一种这样的路径。这应该是
	 * 足够的，因为对于这样的路径基本上只有一个绩效指标
	 * （总成本）。
	 */
	if (fc_bitindexpaths != NIL)
	{
		Path	   *fc_bitmapqual;
		BitmapHeapPath *fc_bpath;

		fc_bitmapqual = fc_choose_bitmap_and(fc_root, fc_rel, fc_bitindexpaths);
		fc_bpath = create_bitmap_heap_path(fc_root, fc_rel, fc_bitmapqual,
										fc_rel->lateral_relids, 1.0, 0);
		add_path(fc_rel, (Path *) fc_bpath);

		/* 创建部分位图堆路径 */
		if (fc_rel->consider_parallel && fc_rel->lateral_relids == NULL)
			create_partial_bitmap_paths(fc_root, fc_rel, fc_bitmapqual);
	}

	/*
	 * 同样，如果我们找到了任何可用的内容，为最有前途的
	 * 连接位图索引路径组合生成 BitmapHeapPaths。我们的策略
	 * 是为可用的每个不同参数化生成这样一条路径。
	 * 这可能看起来非常昂贵，但通常不会有很多不同的
	 * 参数化。（此逻辑与 consider_index_join_clauses 中的
	 * 非常相似，但我们处理的是整个路径而不是单个子句。）
	 */
	if (fc_bitjoinpaths != NIL)
	{
		List	   *fc_all_path_outers;
		ListCell   *fc_lc;

		
/* 识别在 bitjoinpaths 中看到的每个不同的参数化 */
		fc_all_path_outers = NIL;
		foreach(fc_lc, fc_bitjoinpaths)
		{
			Path	   *fc_path = (Path *) lfirst(fc_lc);
			Relids		fc_required_outer = PATH_REQ_OUTER(fc_path);

			if (!fc_bms_equal_any(fc_required_outer, fc_all_path_outers))
				fc_all_path_outers = lappend(fc_all_path_outers, fc_required_outer);
		}

		/* 现在，对于每个不同的参数化集... */
		foreach(fc_lc, fc_all_path_outers)
		{
			Relids		fc_max_outers = (Relids) lfirst(fc_lc);
			List	   *fc_this_path_set;
			Path	   *fc_bitmapqual;
			Relids		fc_required_outer;
			double		fc_loop_count;
			BitmapHeapPath *fc_bpath;
			ListCell   *fc_lcp;

			/* 识别所有需要不超过该限制的位图连接路径 */
			fc_this_path_set = NIL;
			foreach(fc_lcp, fc_bitjoinpaths)
			{
				Path	   *fc_path = (Path *) lfirst(fc_lcp);

				if (bms_is_subset(PATH_REQ_OUTER(fc_path), fc_max_outers))
					fc_this_path_set = lappend(fc_this_path_set, fc_path);
			}

			/*
			 * 添加限制位图路径，因为它们可以与任何连接路径一起使用。
			 */
			fc_this_path_set = list_concat(fc_this_path_set, fc_bitindexpaths);

			/* 为此参数化选择最佳的 AND 组合 */
			fc_bitmapqual = fc_choose_bitmap_and(fc_root, fc_rel, fc_this_path_set);

			/* 将该路径推入混合中 */
			fc_required_outer = PATH_REQ_OUTER(fc_bitmapqual);
			fc_loop_count = get_loop_count(fc_root, fc_rel->relid, fc_required_outer);
			fc_bpath = create_bitmap_heap_path(fc_root, fc_rel, fc_bitmapqual,
											fc_required_outer, fc_loop_count, 0);
			add_path(fc_rel, (Path *) fc_bpath);
		}
	}
}

/*
 * consider_index_join_clauses
 *	  给定一组索引的连接子句，决定要构建哪些参数化的索引路径。
 *
 * 普通索引路径直接发送到 add_path，而潜在的位图索引路径则添加到 *bitindexpaths 以便后续处理。
 *
 * 'rel' 是索引的堆关系
 * 'index' 是我们想要生成路径的索引
 * 'rclauseset' 是可索引的限制子句集
 * 'jclauseset' 是可索引的简单连接子句集
 * 'eclauseset' 是来自等价类的可索引子句集合
 * '*bitindexpaths' 是添加位图路径的列表
 */
static void fc_consider_index_join_clauses(PlannerInfo *fc_root, RelOptInfo *fc_rel,
							IndexOptInfo *fc_index,
							IndexClauseSet *fc_rclauseset,
							IndexClauseSet *fc_jclauseset,
							IndexClauseSet *fc_eclauseset,
							List **fc_bitindexpaths)
{
	int			fc_considered_clauses = 0;
	List	   *fc_considered_relids = NIL;
	int			fc_indexcol;

	/*
	 * 此策略旨在识别每个潜在有用的外部关系集，这些外部关系可以提供可索引的连接子句。对于每个这样的集合,
	 * 从这些外部关系中选择所有可用的连接子句，添加所有可索引的限制子句，并为该子句集合生成普通和/或位图索引路径。
	 * 这一切都是基于一个假设：将子句作为索引条件应用总是比作为筛选器（qpqual）应用更好；
	 * 如果我们从索引条件中省略它，则可用的子句最终将作为筛选器应用。
	 *
	 * 这看起来昂贵，但在大多数实际情况下，考虑的外部关系集合不会太多。当这种情况不成立时，我们使用一个启发式算法：
	 * 将考虑的外部关系集合的数量限制为考虑的子句数量的倍数。（不过我们总是会考虑使用每个单独的连接子句。）
	 *
	 * 为了简化选择相关子句，我们将每组外部关系表示为一个最大子句_relids 集合——也就是说，索引关系本身也包含在 relids 集合中。
	 * considered_relids 列出我们已经尝试过的所有 relids 集合。
	 */
	for (fc_indexcol = 0; fc_indexcol < fc_index->nkeycolumns; fc_indexcol++)
	{
		/* 考虑每个适用的简单连接子句 */
		fc_considered_clauses += list_length(fc_jclauseset->indexclauses[fc_indexcol]);
		fc_consider_index_join_outer_rels(fc_root, fc_rel, fc_index,
									   fc_rclauseset, fc_jclauseset, fc_eclauseset,
									   fc_bitindexpaths,
									   fc_jclauseset->indexclauses[fc_indexcol],
									   fc_considered_clauses,
									   &fc_considered_relids);
		/* 考虑每个适用的等价类连接子句 */
		fc_considered_clauses += list_length(fc_eclauseset->indexclauses[fc_indexcol]);
		fc_consider_index_join_outer_rels(fc_root, fc_rel, fc_index,
									   fc_rclauseset, fc_jclauseset, fc_eclauseset,
									   fc_bitindexpaths,
									   fc_eclauseset->indexclauses[fc_indexcol],
									   fc_considered_clauses,
									   &fc_considered_relids);
	}
}

/*
 * consider_index_join_outer_rels
 *	  根据子句列表中的子句 relids 生成参数化路径。
 *
 * 该函数是 consider_index_join_clauses 的主要部分；有关理由请参见其中的注释。
 *
 * 'rel'、'index'、'rclauseset'、'jclauseset'、'eclauseset' 和
 *		'bitindexpaths' 如上所述
 * 'indexjoinclauses' 是一个连接子句的 IndexClauses 列表
 * 'considered_clauses' 是考虑的子句总数（截至目前）
 * '*considered_relids' 是已考虑的所有 relids 集合的列表
 */
static void fc_consider_index_join_outer_rels(PlannerInfo *fc_root, RelOptInfo *fc_rel,
							   IndexOptInfo *fc_index,
							   IndexClauseSet *fc_rclauseset,
							   IndexClauseSet *fc_jclauseset,
							   IndexClauseSet *fc_eclauseset,
							   List **fc_bitindexpaths,
							   List *fc_indexjoinclauses,
							   int fc_considered_clauses,
							   List **fc_considered_relids)
{
	ListCell   *fc_lc;

	/* 检查给定列表中每个连接子句的 relids */
	foreach(fc_lc, fc_indexjoinclauses)
	{
		IndexClause *fc_iclause = (IndexClause *) lfirst(fc_lc);
		Relids		fc_clause_relids = fc_iclause->rinfo->clause_relids;
		EquivalenceClass *fc_parent_ec = fc_iclause->rinfo->parent_ec;
		int			fc_num_considered_relids;

		/* 如果我们已经尝试过它的 relids 集合，则无需重复尝试 */
		if (fc_bms_equal_any(fc_clause_relids, *fc_considered_relids))
			continue;

		/*
		 * 生成该子句的 relids 集合与每个先前尝试的集合的并集。
		 * 这确保我们与每个有趣的前置子句子集一起尝试此子句。
		 * 不过，为了避免在存在许多子句时规划时间的指数增长，
		 * 将接受的 relid 集合数量限制为 10 * considered_clauses。
		 *
		 * 注意：get_join_index_paths 将条目附加到 *considered_relids， 
		 * 但我们在此循环内无需访问这些新添加的条目，因此不使用 foreach()。
		 * 如果我们访问它们也不会造成实质性伤害，因为子集检查将拒绝它们；
		 * 但这会浪费一些周期。
		 */
		fc_num_considered_relids = list_length(*fc_considered_relids);
		for (int fc_pos = 0; fc_pos < fc_num_considered_relids; fc_pos++)
		{
			Relids		fc_oldrelids = (Relids) list_nth(*fc_considered_relids, fc_pos);

			/*
			 * 如果两个集合彼此为子集，则不可能存在新的集合。
			 * 这不是冗余的完整测试，但它简单且便宜。
			 * get_join_index_paths 将仔细检查我们是否已经生成了相同的 relids 集合。
			 */
			if (bms_subset_compare(fc_clause_relids, fc_oldrelids) != BMS_DIFFERENT)
				continue;

			/*
			 * 如果该子句是从一个等价类派生的，则子句列表中可能包含其他从同一等价类派生的子句。
			 * 我们不应考虑将该子句与这些子句之一结合生成有用的不同参数化；因此，如果使用了 oldrelids
			 * 时，来自同一等价类的任何子句已经包含，则跳过。
			 */
			if (fc_parent_ec &&
				fc_eclass_already_used(fc_parent_ec, fc_oldrelids,
									fc_indexjoinclauses))
				continue;

			/*
			 * 如果考虑的 relid 集合数量超过我们的启发式限制，停止考虑子句组合。
			 * 不过，我们仍会单独考虑当前子句（在此循环下面）。
			 */
			if (list_length(*fc_considered_relids) >= 10 * fc_considered_clauses)
				break;

			/* 好的，尝试并集 */
			get_join_index_paths(fc_root, fc_rel, fc_index,
								 fc_rclauseset, fc_jclauseset, fc_eclauseset,
								 fc_bitindexpaths,
								 bms_union(fc_clause_relids, fc_oldrelids),
								 fc_considered_relids);
		}

		/* 也单独尝试这组 relids */ 
		get_join_index_paths(fc_root, fc_rel, fc_index,
							 fc_rclauseset, fc_jclauseset, fc_eclauseset,
							 fc_bitindexpaths,
							 fc_clause_relids,
							 fc_considered_relids);
	}
}


/*
 * get_join_index_paths
 *	  生成使用指定外部关系的子句的索引路径。
 *	  除了生成路径外，如果 relids 尚未存在于 *considered_relids 中，则将其添加上。
 *
 * 这是 consider_index_join_clauses 的主要工作函数；有关理由，请参阅其中的说明。
 *
 * 'rel', 'index', 'rclauseset', 'jclauseset', 'eclauseset',
 *		'bitindexpaths', 'considered_relids' 如上所述
 * 'relids' 是当前需要考虑的 relids 集（目标 rel 加上一个或多个外部 rel）
 */
static void get_join_index_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel,
					 IndexOptInfo *fc_index,
					 IndexClauseSet *fc_rclauseset,
					 IndexClauseSet *fc_jclauseset,
					 IndexClauseSet *fc_eclauseset,
					 List **fc_bitindexpaths,
					 Relids fc_relids,
					 List **fc_considered_relids)
{
	IndexClauseSet fc_clauseset;
	int			fc_indexcol;

	/* 如果我们已经考虑了这个 relids 集，则不要重复工作 */
	if (fc_bms_equal_any(fc_relids, *fc_considered_relids))
		return;

	/* 确定可用于此 relids 集的 indexclauses */
	MemSet(&fc_clauseset, 0, sizeof(fc_clauseset));

	for (fc_indexcol = 0; fc_indexcol < fc_index->nkeycolumns; fc_indexcol++)
	{
		ListCell   *fc_lc;

		/* 首先找到适用的简单连接子句 */
		foreach(fc_lc, fc_jclauseset->indexclauses[fc_indexcol])
		{
			IndexClause *fc_iclause = (IndexClause *) lfirst(fc_lc);

			if (bms_is_subset(fc_iclause->rinfo->clause_relids, fc_relids))
				fc_clauseset.indexclauses[fc_indexcol] =
					lappend(fc_clauseset.indexclauses[fc_indexcol], fc_iclause);
		}

		/*
		 * 添加适用的 eclass 连接子句。为每个列生成的子句是冗余的
		 * （参见 generate_implied_equalities_for_column），
		 * 因此我们最多需要一个。这是使用所有可用索引子句的一般
		 * 规则的唯一例外。
		 */
		foreach(fc_lc, fc_eclauseset->indexclauses[fc_indexcol])
		{
			IndexClause *fc_iclause = (IndexClause *) lfirst(fc_lc);

			if (bms_is_subset(fc_iclause->rinfo->clause_relids, fc_relids))
			{
				fc_clauseset.indexclauses[fc_indexcol] =
					lappend(fc_clauseset.indexclauses[fc_indexcol], fc_iclause);
				break;
			}
		}

		/* 添加限制子句 */
		fc_clauseset.indexclauses[fc_indexcol] =
			list_concat(fc_clauseset.indexclauses[fc_indexcol],
						fc_rclauseset->indexclauses[fc_indexcol]);

		if (fc_clauseset.indexclauses[fc_indexcol] != NIL)
			fc_clauseset.nonempty = true;
	}

	/* 我们应该找到了某些东西，否则调用者传递了不合理的 relids */
	Assert(fc_clauseset.nonempty);

	/* 使用收集的子句构建索引路径 */
	get_index_paths(fc_root, fc_rel, fc_index, &fc_clauseset, fc_bitindexpaths);

	/*
	 * 记住我们考虑了这个 relids 集的路径。
	 */
	*fc_considered_relids = lappend(*fc_considered_relids, fc_relids);
}

/*
 * eclass_already_used
 *		如果从指定的等价类生成了任何可与 oldrelids 一起使用的连接子句，则为真。
 */
static bool fc_eclass_already_used(EquivalenceClass *fc_parent_ec, Relids fc_oldrelids,
					List *fc_indexjoinclauses)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_indexjoinclauses)
	{
		IndexClause *fc_iclause = (IndexClause *) lfirst(fc_lc);
		RestrictInfo *fc_rinfo = fc_iclause->rinfo;

		if (fc_rinfo->parent_ec == fc_parent_ec &&
			bms_is_subset(fc_rinfo->clause_relids, fc_oldrelids))
			return true;
	}
	return false;
}

/*
 * bms_equal_any
 *		如果 relids 与 relids_list 的任何成员相等，则为真。
 *
 * 也许这将来应该放在 bitmapset.c 中。
 */
static bool fc_bms_equal_any(Relids fc_relids, List *fc_relids_list)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_relids_list)
	{
		if (bms_equal(fc_relids, (Relids) lfirst(fc_lc)))
			return true;
	}
	return false;
}


/*
 * get_index_paths
 *	  给定一个索引和一组索引子句，为其构造 IndexPaths。
 *
 * 普通的 indexpaths 直接发送到 add_path，而潜在的
 * bitmap indexpaths 添加到 *bitindexpaths 以供后续处理。
 *
 * 这是一个相对简单的前端，用于 build_index_paths()。它存在的原因主要是为了正确处理 ScalarArrayOpExpr 资格。如果索引 AM 原生支持它们，我们应该将它们直接包含在简单的索引路径中。如果不支持，我们应该在构建简单的索引路径时排除它们，然后单独尝试将它们包含在位图路径中。此外，我们还应该考虑排除低阶 ScalarArrayOpExpr 资格，以便创建有序路径。
 */
static void get_index_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel,
				IndexOptInfo *fc_index, IndexClauseSet *fc_clauses,
				List **fc_bitindexpaths)
{
	List	   *fc_indexpaths;
	bool		fc_skip_nonnative_saop = false;
	bool		fc_skip_lower_saop = false;
	ListCell   *fc_lc;

	/*
	 * 使用这些子句构建简单的索引路径。仅在索引 AM 原生支持
	 * ScalarArrayOpExpr 子句时允许此类子句，并且跳过第一个之后的
	 * 索引列的任何此类子句（以便尽可能生成有序路径）。
	 */
	fc_indexpaths = fc_build_index_paths(fc_root, fc_rel,
								   fc_index, fc_clauses,
								   fc_index->predOK,
								   ST_ANYSCAN,
								   &fc_skip_nonnative_saop,
								   &fc_skip_lower_saop);

	/*
	 * 如果我们在支持 ScalarArrayOpExpr 的 AM 上跳过了任何低阶的
	 * ScalarArrayOpExpr，则再次尝试包括这些子句。这将生成
	 * 选择性更强但没有顺序的路径。
	 */
	if (fc_skip_lower_saop)
	{
		fc_indexpaths = list_concat(fc_indexpaths,
								 fc_build_index_paths(fc_root, fc_rel,
												   fc_index, fc_clauses,
												   fc_index->predOK,
												   ST_ANYSCAN,
												   &fc_skip_nonnative_saop,
												   NULL));
	}

	/*
	 * 向 add_path 提交所有可以形成普通 IndexScan 计划的路径。
	 * （普通 IndexPath 可以表示普通 IndexScan 或 IndexOnlyScan，
	 * 但在这里，该区别并不重要。然而，某些索引可能仅支持位图扫描，
	 * 这些我们不能在这里提交给 add_path。）
	 *
	 * 此外，挑选出可用作位图扫描的那些。为此，我们需要丢弃
	 * 不支持位图扫描的索引，并且我们只对具有某种选择性的路径
	 * 感兴趣；我们应该丢弃任何仅为排序目的生成的内容。
	 */
	foreach(fc_lc, fc_indexpaths)
	{
		IndexPath  *fc_ipath = (IndexPath *) lfirst(fc_lc);

		if (fc_index->amhasgettuple)
			add_path(fc_rel, (Path *) fc_ipath);

		if (fc_index->amhasgetbitmap &&
			(fc_ipath->path.pathkeys == NIL ||
			 fc_ipath->indexselectivity < 1.0))
			*fc_bitindexpaths = lappend(*fc_bitindexpaths, fc_ipath);
	}

	/*
	 * 如果存在索引无法原生处理的 ScalarArrayOpExpr 条件，
	 * 则生成基于执行器管理的 ScalarArrayOpExpr 的位图扫描路径。
	 */
	if (fc_skip_nonnative_saop)
	{
		fc_indexpaths = fc_build_index_paths(fc_root, fc_rel,
									   fc_index, fc_clauses,
									   false,
									   ST_BITMAPSCAN,
									   NULL,
									   NULL);
		*fc_bitindexpaths = list_concat(*fc_bitindexpaths, fc_indexpaths);
	}
}

/*
 * build_index_paths
 *	  给定一个索引和一组索引条件，构造零个
 *	  或多个 IndexPaths。它还构造零个
 *	  或多个部分 IndexPaths。
 *
 * 我们返回一个路径列表，因为（1）此例程检查一些
 * 应导致我们不生成任何 IndexPath 的情况，以及（2）在某些
 * 情况下我们想同时考虑正向和反向扫描，以便获得
 * 两种排序顺序。请注意，这些路径只是返回
 * 给调用者，而不是立即传递给 add_path()。
 *
 * 在顶层，有用谓词应该正好是索引的 predOK 标志
 * （即，如果它具有从限制条件中证明的谓词，则为真）。
 * 在处理 OR 子句的某个分支时，如果该谓词要求
 * 当前 OR 列表被证明，有用谓词应该为真。
 * 请注意，如果索引具有无法证明的谓词，
 * 此例程绝不应被调用。
 *
 * scantype 指示我们是否想创建普通的索引扫描、
 * 位图索引扫描，或两者都要。当它是 ST_BITMAPSCAN 时，
 * 在决定是否生成一个路径时，我们将不考虑
 * 索引顺序。
 *
 * 如果 skip_nonnative_saop 非 NULL，我们会忽略
 * ScalarArrayOpExpr 条件，除非索引 AM 直接支持它们，
 * 如果我们找到任何此类条件，则将 *skip_nonnative_saop
 * 设置为 true（调用者必须将变量初始化为 false）。
 * 如果为 NULL，则我们不忽略 ScalarArrayOpExpr 条件。
 *
 * 如果 skip_lower_saop 非 NULL，我们会忽略
 * 非第一个索引列的 ScalarArrayOpExpr 条件，
 * 如果我们找到任何此类条件，则将 *skip_lower_saop
 * 设置为 true（调用者必须将变量初始化为 false）。
 * 如果为 NULL，我们不会忽略非第一个 ScalarArrayOpExpr 条件，
 * 但这会导致考虑扫描的输出是无序的。
 *
 * 'rel' 是索引的堆关系
 * 'index' 是我们想要生成路径的索引
 * 'clauses' 是可索引条件的集合（IndexClause 节点）
 * 'useful_predicate' 指示索引是否具有一个有用的谓词
 * 'scantype' 指示我们需要普通扫描还是位图扫描支持
 * 'skip_nonnative_saop' 指示是否接受 SAOP，如果索引 AM 不支持
 * 'skip_lower_saop' 指示是否接受非第一列的 SAOP
 */
static List * fc_build_index_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel,
				  IndexOptInfo *fc_index, IndexClauseSet *fc_clauses,
				  bool fc_useful_predicate,
				  ScanTypeControl fc_scantype,
				  bool *fc_skip_nonnative_saop,
				  bool *fc_skip_lower_saop)
{
	List	   *fc_result = NIL;
	IndexPath  *fc_ipath;
	List	   *fc_index_clauses;
	Relids		fc_outer_relids;
	double		fc_loop_count;
	List	   *fc_orderbyclauses;
	List	   *fc_orderbyclausecols;
	List	   *fc_index_pathkeys;
	List	   *fc_useful_pathkeys;
	bool		fc_found_lower_saop_clause;
	bool		fc_pathkeys_possibly_useful;
	bool		fc_index_is_ordered;
	bool		fc_index_only_scan;
	int			fc_indexcol;

	/*
	 * 检查索引是否支持所需的扫描类型
	 */
	switch (fc_scantype)
	{
		case ST_INDEXSCAN:
			if (!fc_index->amhasgettuple)
				return NIL;
			break;
		case ST_BITMAPSCAN:
			if (!fc_index->amhasgetbitmap)
				return NIL;
			break;
		case ST_ANYSCAN:
			/* 其中任一或两者都可以 */
			break;
	}

	/*
	 * 1. 将每列的 IndexClause 列表合并为一个总体列表。
	 *
	 * 在结果列表中，条件按索引键排序，以便列号形成一个
	 * 非递减序列。（此顺序由 btree 以及可能的其他地方依赖。）
	 * 如果索引 AM 允许，则列表可以为空。
	 *
	 * 如果我们接受非第一索引列的 ScalarArrayOpExpr
	 * 索引条件，则 found_lower_saop_clause 被设置为 true。
	 * 这防止我们假设扫描结果是有序的。（实际上，如果
	 * 所有早期列都有相等约束，结果仍然是有序的，但让
	 * 这个代码知道这种细化似乎成本过高且不模块化。）
	 *
	 * 我们还构建一个 Relids 集，显示所选条件所需的外部关系。
	 * 任何 lateral_relids 都包含在内，但不会被其他方式考虑。
	 */
	fc_index_clauses = NIL;
	fc_found_lower_saop_clause = false;
	fc_outer_relids = bms_copy(fc_rel->lateral_relids);
	for (fc_indexcol = 0; fc_indexcol < fc_index->nkeycolumns; fc_indexcol++)
	{
		ListCell   *fc_lc;

		foreach(fc_lc, fc_clauses->indexclauses[fc_indexcol])
		{
			IndexClause *fc_iclause = (IndexClause *) lfirst(fc_lc);
			RestrictInfo *fc_rinfo = fc_iclause->rinfo;

			/* 我们可能需要省略 ScalarArrayOpExpr 条件 */
			if (IsA(fc_rinfo->clause, ScalarArrayOpExpr))
			{
				if (!fc_index->amsearcharray)
				{
					if (fc_skip_nonnative_saop)
					{
						/* 忽略，因为索引不支持 */
						*fc_skip_nonnative_saop = true;
						continue;
					}
					/* 调用者最好仅将其用于位图扫描 */
					Assert(fc_scantype == ST_BITMAPSCAN);
				}
				if (fc_indexcol > 0)
				{
					if (fc_skip_lower_saop)
					{
						/* 调用者不想丢失索引顺序 */
						*fc_skip_lower_saop = true;
						continue;
					}
					fc_found_lower_saop_clause = true;
				}
			}

			/* 包含此条件是可以的 */
			fc_index_clauses = lappend(fc_index_clauses, fc_iclause);
			fc_outer_relids = bms_add_members(fc_outer_relids,
										   fc_rinfo->clause_relids);
		}

		/*
		 * 如果没有条件匹配第一个索引列，则检查 amoptionalkey
		 * 限制。除非至少存在一个索引条件，否则我们不能
		 * 生成带有 amoptionalkey = false 的索引扫描。
		 * （在处理第一个之后的列时，此测试不能失败。
		 * 第一个之后的列没有任何条件是始终可以的。）
		 */
		if (fc_index_clauses == NIL && !fc_index->amoptionalkey)
			return NIL;
	}

	/* 我们不希望索引的关系本身列在 outer_relids 中 */
	fc_outer_relids = bms_del_member(fc_outer_relids, fc_rel->relid);
	/* 强制规定，如果为空，outer_relids 需精确为 NULL */
	if (bms_is_empty(fc_outer_relids))
		fc_outer_relids = NULL;

	/* 计算成本估算目的的 loop_count */
	fc_loop_count = get_loop_count(fc_root, fc_rel->relid, fc_outer_relids);

	/*
	 * 2. 计算描述索引顺序的 pathkeys（如果有），然后查看
	 * 有多少实际上对这个查询有用。如果我们仅试图构建
	 * 位图索引扫描，或者必须假设扫描是无序的，
	 * 那么这并不相关。
	 */
	fc_pathkeys_possibly_useful = (fc_scantype != ST_BITMAPSCAN &&
								!fc_found_lower_saop_clause &&
								has_useful_pathkeys(fc_root, fc_rel));
	fc_index_is_ordered = (fc_index->sortopfamily != NULL);
	if (fc_index_is_ordered && fc_pathkeys_possibly_useful)
	{
		fc_index_pathkeys = build_index_pathkeys(fc_root, fc_index,
											  ForwardScanDirection);
		fc_useful_pathkeys = truncate_useless_pathkeys(fc_root, fc_rel,
													fc_index_pathkeys);
		fc_orderbyclauses = NIL;
		fc_orderbyclausecols = NIL;
	}
	else if (fc_index->amcanorderbyop && fc_pathkeys_possibly_useful)
	{
		/* 查看我们是否能为 query_pathkeys 生成排序运算符 */
		fc_match_pathkeys_to_index(fc_index, fc_root->query_pathkeys,
								&fc_orderbyclauses,
								&fc_orderbyclausecols);
		if (fc_orderbyclauses)
			fc_useful_pathkeys = fc_root->query_pathkeys;
		else
			fc_useful_pathkeys = NIL;
	}
	else
	{
		fc_useful_pathkeys = NIL;
		fc_orderbyclauses = NIL;
		fc_orderbyclausecols = NIL;
	}

	/*
	 * 3. 检查是否可能进行仅索引扫描。
	 * 如果我们不构建普通的索引扫描，
	 * 这就不相关，因为位图扫描无论如何不支持
	 * 索引数据检索。
	 */
	fc_index_only_scan = (fc_scantype != ST_BITMAPSCAN &&
					   fc_check_index_only(fc_rel, fc_index));

	
/*
	 * 4. 如果当前子句中有相关的限制子句，生成一个索引扫描路径，或者索引顺序对后续合并或最终输出顺序可能有用，或者索引有一个有用的谓词，或者可以进行仅索引扫描。
	 */
	if (fc_index_clauses != NIL || fc_useful_pathkeys != NIL || fc_useful_predicate ||
		fc_index_only_scan)
	{
		fc_ipath = create_index_path(fc_root, fc_index,
								  fc_index_clauses,
								  fc_orderbyclauses,
								  fc_orderbyclausecols,
								  fc_useful_pathkeys,
								  fc_index_is_ordered ?
								  ForwardScanDirection :
								  NoMovementScanDirection,
								  fc_index_only_scan,
								  fc_outer_relids,
								  fc_loop_count,
								  false);
		fc_result = lappend(fc_result, fc_ipath);

		/*
		 * 如果适当，考虑并行索引扫描。我们不允许对位图索引扫描进行并行索引扫描。
		 */
		if (fc_index->amcanparallel &&
			fc_rel->consider_parallel && fc_outer_relids == NULL &&
			fc_scantype != ST_BITMAPSCAN)
		{
			fc_ipath = create_index_path(fc_root, fc_index,
									  fc_index_clauses,
									  fc_orderbyclauses,
									  fc_orderbyclausecols,
									  fc_useful_pathkeys,
									  fc_index_is_ordered ?
									  ForwardScanDirection :
									  NoMovementScanDirection,
									  fc_index_only_scan,
									  fc_outer_relids,
									  fc_loop_count,
									  true);

			/*
			 * 如果在成本评估后发现不值得使用并行工作者，则直接释放它。
			 */
			if (fc_ipath->path.parallel_workers > 0)
				add_partial_path(fc_rel, (Path *) fc_ipath);
			else
				pfree(fc_ipath);
		}
	}

	/*
	 * 5. 如果索引是有序的，向后扫描可能会很有趣。
	 */
	if (fc_index_is_ordered && fc_pathkeys_possibly_useful)
	{
		fc_index_pathkeys = build_index_pathkeys(fc_root, fc_index,
											  BackwardScanDirection);
		fc_useful_pathkeys = truncate_useless_pathkeys(fc_root, fc_rel,
													fc_index_pathkeys);
		if (fc_useful_pathkeys != NIL)
		{
			fc_ipath = create_index_path(fc_root, fc_index,
									  fc_index_clauses,
									  NIL,
									  NIL,
									  fc_useful_pathkeys,
									  BackwardScanDirection,
									  fc_index_only_scan,
									  fc_outer_relids,
									  fc_loop_count,
									  false);
			fc_result = lappend(fc_result, fc_ipath);

			/* 如果适当，考虑并行索引扫描 */
			if (fc_index->amcanparallel &&
				fc_rel->consider_parallel && fc_outer_relids == NULL &&
				fc_scantype != ST_BITMAPSCAN)
			{
				fc_ipath = create_index_path(fc_root, fc_index,
										  fc_index_clauses,
										  NIL,
										  NIL,
										  fc_useful_pathkeys,
										  BackwardScanDirection,
										  fc_index_only_scan,
										  fc_outer_relids,
										  fc_loop_count,
										  true);

				/*
				 * 如果在成本评估后发现不值得使用并行工作者，则直接释放它。
				 */
				if (fc_ipath->path.parallel_workers > 0)
					add_partial_path(fc_rel, (Path *) fc_ipath);
				else
					pfree(fc_ipath);
			}
		}
	}

	return fc_result;
}

/*
 * build_paths_for_OR
 *	  给定来自 OR 子句一个分支的限制子句列表，为关系构建所有匹配的 IndexPaths。
 *
 * 在这里，我们必须扫描关系的所有索引，因为位图 OR 树可以使用多个索引。
 *
 * 调用者实际上提供了两个限制子句列表：一些是 "当前" 的，另一些是 "其他" 的。两个列表都可以自由使用以匹配索引的键，但索引必须至少使用一个 "当前" 子句才能被认为是可用的。这样做的动机是类似于
 *		WHERE (x = 42) AND (... OR (y = 52 AND z = 77) OR ....)
 * 在考虑 OR 的 y/z 子句时，我们可以将 "x = 42" 作为可用索引条件之一；但我们不应该将子句与任何仅在 x 上的索引匹配，因为这样的路径已经在上层生成过。因此，我们可以在 x,y,z 上使用索引，或者在 x,y 上使用索引用于 OR 子句，但不能仅在 x 上使用索引。
 * 在处理部分索引时，如果索引谓词与其中一个 "当前" 子句匹配，也使得索引可用。
 *
 * 'rel' 是我们想要生成索引路径的关系
 * 'clauses' 是当前的子句列表（RestrictInfo 节点）
 * 'other_clauses' 是附加的上层子句列表
 */
static List * fc_build_paths_for_OR(PlannerInfo *fc_root, RelOptInfo *fc_rel,
				   List *fc_clauses, List *fc_other_clauses)
{
	List	   *fc_result = NIL;
	List	   *fc_all_clauses = NIL;	/* 直到需要时才计算 */
	ListCell   *fc_lc;

	foreach(fc_lc, fc_rel->indexlist)
	{
		IndexOptInfo *fc_index = (IndexOptInfo *) lfirst(fc_lc);
		IndexClauseSet fc_clauseset;
		List	   *fc_indexpaths;
		bool		fc_useful_predicate;

		/* 如果索引不支持位图扫描，则忽略索引 */
		if (!fc_index->amhasgetbitmap)
			continue;

		/*
		 * 忽略不符合查询的部分索引。如果部分索引标记为 predOK，那么我们知道它是可以的。否则，我们必须测试添加的子句是否足以推导出谓词。如果可以，我们可以在当前上下文中使用该索引。
		 *
		 * 我们将 useful_predicate 设置为 true，仅当使用当前子句集证明了谓词。这是为了防止将 predOK 索引与 OR 的一支匹配，这将是合法的但无谓低效的计划。（更好的计划只需扫描 predOK 索引，没必要使用 OR。）
		 */
		fc_useful_predicate = false;
		if (fc_index->indpred != NIL)
		{
			if (fc_index->predOK)
			{
				/* 可用，但不设置 useful_predicate */
			}
			else
			{
				/* 如果尚未完成，形成 all_clauses */
				if (fc_all_clauses == NIL)
					fc_all_clauses = list_concat_copy(fc_clauses, fc_other_clauses);

				if (!predicate_implied_by(fc_index->indpred, fc_all_clauses, false))
					continue;	/* 完全无法使用 */

				if (!predicate_implied_by(fc_index->indpred, fc_other_clauses, false))
					fc_useful_predicate = true;
			}
		}

		/*
		 * 确定可以匹配该索引的限制子句。
		 */
		MemSet(&fc_clauseset, 0, sizeof(fc_clauseset));
		fc_match_clauses_to_index(fc_root, fc_clauses, fc_index, &fc_clauseset);

		/*
		 * 如果到目前为止没有匹配，并且索引谓词没有用，我们
		 * 不想要它。
		 */
		if (!fc_clauseset.nonempty && !fc_useful_predicate)
			continue;

		/*
		 * 将“其他”限制子句添加到子句集中。
		 */
		fc_match_clauses_to_index(fc_root, fc_other_clauses, fc_index, &fc_clauseset);

		/*
		 * 如果可能，构造路径。
		 */
		fc_indexpaths = fc_build_index_paths(fc_root, fc_rel,
									   fc_index, &fc_clauseset,
									   fc_useful_predicate,
									   ST_BITMAPSCAN,
									   NULL,
									   NULL);
		fc_result = list_concat(fc_result, fc_indexpaths);
	}

	return fc_result;
}

/*
 * generate_bitmap_or_paths
 *		查看子句列表以查找OR子句，并为每一个我们可以以这种方式处理的子句生成
 *		一个 BitmapOrPath。返回生成的 BitmapOrPaths 的列表。
 *
 * other_clauses 是一组额外的子句，可以被视为在生成 indexquals 时为真，
 * 但不用于搜索 OR。 （参见 build_paths_for_OR() 以了解动机。）
 */
static List * fc_generate_bitmap_or_paths(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						 List *fc_clauses, List *fc_other_clauses)
{
	List	   *fc_result = NIL;
	List	   *fc_all_clauses;
	ListCell   *fc_lc;

	/*
	 * 我们可以同时使用当前子句和其他子句作为 build_paths_for_OR 的上下文；
	 * 不需要从列表中删除 OR。
	 */
	fc_all_clauses = list_concat_copy(fc_clauses, fc_other_clauses);

	foreach(fc_lc, fc_clauses)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);
		List	   *fc_pathlist;
		Path	   *fc_bitmapqual;
		ListCell   *fc_j;

		/* 忽略不是 OR 的 RestrictInfos */
		if (!restriction_is_or_clause(fc_rinfo))
			continue;

		/*
		 * 我们必须能够将至少一个索引与 OR 的每个分支匹配，
		 * 否则我们无法使用它。
		 */
		fc_pathlist = NIL;
		foreach(fc_j, ((BoolExpr *) fc_rinfo->orclause)->args)
		{
			Node	   *fc_orarg = (Node *) lfirst(fc_j);
			List	   *fc_indlist;

			/* OR 参数应该是 AND 或子 RestrictInfos */
			if (is_andclause(fc_orarg))
			{
				List	   *fc_andargs = ((BoolExpr *) fc_orarg)->args;

				fc_indlist = fc_build_paths_for_OR(fc_root, fc_rel,
											 fc_andargs,
											 fc_all_clauses);

				/* 递归以防有子 OR */
				fc_indlist = list_concat(fc_indlist,
									  fc_generate_bitmap_or_paths(fc_root, fc_rel,
															   fc_andargs,
															   fc_all_clauses));
			}
			else
			{
				RestrictInfo *fc_rinfo = castNode(RestrictInfo, fc_orarg);
				List	   *fc_orargs;

				Assert(!restriction_is_or_clause(fc_rinfo));
				fc_orargs = list_make1(fc_rinfo);

				fc_indlist = fc_build_paths_for_OR(fc_root, fc_rel,
											 fc_orargs,
											 fc_all_clauses);
			}

			/*
			 * 如果没有匹配到这个分支，我们无法处理这个 OR
			 * 子句。
			 */
			if (fc_indlist == NIL)
			{
				fc_pathlist = NIL;
				break;
			}

			/*
			 * 好吧，选择最有前景的 AND 组合，并将其添加到
			 * pathlist。
			 */
			fc_bitmapqual = fc_choose_bitmap_and(fc_root, fc_rel, fc_indlist);
			fc_pathlist = lappend(fc_pathlist, fc_bitmapqual);
		}

		/*
		 * 如果我们对每个分支都有匹配，则将它们变成一个
		 * BitmapOrPath，并添加到结果列表中。
		 */
		if (fc_pathlist != NIL)
		{
			fc_bitmapqual = (Path *) create_bitmap_or_path(fc_root, fc_rel, fc_pathlist);
			fc_result = lappend(fc_result, fc_bitmapqual);
		}
	}

	return fc_result;
}


/*
 * choose_bitmap_and
 *		给定一个非空的位图路径列表，将它们与成一个路径。
 *
 * 这是一个非平凡的决策，因为我们可以合法地使用给定路径集的任意子集。
 * 我们想在选择性与计算位图成本之间选择一个合适的折衷方案。
 *
 * 结果可能是输入中的一个，或一个结合多个输入的 BitmapAndPath。
 */
static Path * fc_choose_bitmap_and(PlannerInfo *fc_root, RelOptInfo *fc_rel, List *fc_paths)
{
	int			fc_npaths = list_length(fc_paths);
	PathClauseUsage **fc_pathinfoarray;
	PathClauseUsage *fc_pathinfo;
	List	   *fc_clauselist;
	List	   *fc_bestpaths = NIL;
	Cost		fc_bestcost = 0;
	int			fc_i,
				fc_j;
	ListCell   *fc_l;

	Assert(fc_npaths > 0);			/*
		 * 我们使用 CaseTestExpr 作为强制转换树的基础，
		 * 因为插入源值是非常便宜的。
		 */
	if (fc_npaths == 1)
		return (Path *) linitial(fc_paths);	/* 简单的情况 */

	/*
	 * 理论上我们应该考虑给定路径的每个非空子集。
	 * 在实践中，由于估算的粗糙性质，加上高阶 AND 和 OR 子句的可能影响，
	 * 这似乎显得过于复杂。此外，如果路径数量很多，这将变得完全不切实际，
	 * 因为工作量将以 O(2^N) 增长。
	 *
	 * 作为启发式，我们首先检查使用完全相同的 WHERE 子句 + 索引谓词条件的路径，
	 * 并拒绝该组中除了最便宜扫描的路径以外的所有路径。这主要可以去掉那些包含
	 * 有趣列但也包含不相关列的索引。 （在数据库管理员在创建变体索引时过度
	 * 设计的情况下，这可以大大减少进一步考虑的路径数量。）
	 *
	 * 然后我们将存活下来的路径按最便宜的扫描排序，考虑将该路径单独用作
	 * 位图扫描的基础。然后我们考虑由该路径加上每个后续（更高成本）路径
	 * 形成的 bitmap AND 扫描，如果添加后续路径可以降低估计的总扫描成本。
	 * 这意味着我们考虑大约 O(N^2) 而不是 O(2^N) 的路径组合，这相对可以接受，
	 * 尤其是在因为预过滤步骤而使 N 通常相对较小的情况下。此中最便宜的将被返回。
	 *
	 * 我们将只考虑没有两个索引使用相同 WHERE 子句的 AND 组合。这有点
	 * 笨拙：这是必要的，因为 costsize.c 和 clausesel.c 对冗余子句
	 * 的处理并不智能。它们通常会重复计算冗余子句，导致选择性过小，
	 * 使得冗余的 AND 步骤看起来能够降低总成本。也许总有一天这些代码
	 * 会更智能，我们可以消除这个限制。 （但请注意，这也防止了完全重复的
	 * 输入路径，这可能发生在 match_join_clauses_to_index 找到与
	 * extract_restriction_or_clauses 拿出的 OR 限制子句相同的 OR 联接子句时。）
	 *
	 * 出于同样的理由，我们拒绝一个索引谓词子句重复另一个子句的 AND 组合。
	 * 在这里我们发现有必要更加严格：如果它的任何谓词子句被到目前为止
	 * 使用的 WHERE 子句和谓词子句集所隐含，我们将拒绝部分索引。
	 * 这覆盖了诸如 "x = 42" 的条件与普通索引结合使用后，随后
	 * 执行一个没有子句的部分索引 "WHERE x >= 40 AND x < 50" 的案例。
	 * 部分索引之所以被接受仅仅是因为存在 "x = 42"，因此允许它将部分
	 * 重复计算选择性。 （我们也可以在常规限定子句上使用 predicate_implied_by，
	 * 进行更智能但代价更高的冗余检查 --- 但在大多数情况下简单的相等满足需求。）
	 */

	
/*
	 * 提取子句使用信息并检测使用完全相同子句集的路径；仅保留任何此类组中扫描成本最低的。
	 * 存活的路径被放入数组中以进行qsort排序。
	 */
	fc_pathinfoarray = (PathClauseUsage **)
		palloc(fc_npaths * sizeof(PathClauseUsage *));
	fc_clauselist = NIL;
	fc_npaths = 0;
	foreach(fc_l, fc_paths)
	{
		Path	   *fc_ipath = (Path *) lfirst(fc_l);

		fc_pathinfo = fc_classify_index_clause_usage(fc_ipath, &fc_clauselist);

		/* 如果无法分类，则将其视为与所有其他路径不同 */
		if (fc_pathinfo->unclassifiable)
		{
			fc_pathinfoarray[fc_npaths++] = fc_pathinfo;
			continue;
		}

		for (fc_i = 0; fc_i < fc_npaths; fc_i++)
		{
			if (!fc_pathinfoarray[fc_i]->unclassifiable &&
				bms_equal(fc_pathinfo->clauseids, fc_pathinfoarray[fc_i]->clauseids))
				break;
		}
		if (fc_i < fc_npaths)
		{
			/* 重复的子句ID，保留成本更低的一个 */
			Cost		fc_ncost;
			Cost		fc_ocost;
			Selectivity fc_nselec;
			Selectivity fc_oselec;

			cost_bitmap_tree_node(fc_pathinfo->path, &fc_ncost, &fc_nselec);
			cost_bitmap_tree_node(fc_pathinfoarray[fc_i]->path, &fc_ocost, &fc_oselec);
			if (fc_ncost < fc_ocost)
				fc_pathinfoarray[fc_i] = fc_pathinfo;
		}
		else
		{
			/* 不重复的子句ID，添加到数组中 */
			fc_pathinfoarray[fc_npaths++] = fc_pathinfo;
		}
	}

	/* 如果只有一条存活路径，我们完成了 */
	if (fc_npaths == 1)
		return fc_pathinfoarray[0]->path;

	/* 按索引访问成本对存活路径进行排序 */
	qsort(fc_pathinfoarray, fc_npaths, sizeof(PathClauseUsage *),
		  fc_path_usage_comparator);

	/*
	 * 对于每个存活的索引，将其视为“AND组领导者”，然后查看
	 * 是否添加任何后续索引会导致总成本比之前更低的AND路径。 然后选取成本最低的AND组。
	 *
	 * 注意：没有子句或无法分类的路径将具有
	 * 空的子句ID，以便它们不会被此处的子句ID过滤掉，也不会导致后续路径被其拒绝。
	 */
	for (fc_i = 0; fc_i < fc_npaths; fc_i++)
	{
		Cost		fc_costsofar;
		List	   *fc_qualsofar;
		Bitmapset  *fc_clauseidsofar;

		fc_pathinfo = fc_pathinfoarray[fc_i];
		fc_paths = list_make1(fc_pathinfo->path);
		fc_costsofar = fc_bitmap_scan_cost_est(fc_root, fc_rel, fc_pathinfo->path);
		fc_qualsofar = list_concat_copy(fc_pathinfo->quals, fc_pathinfo->preds);
		fc_clauseidsofar = bms_copy(fc_pathinfo->clauseids);

		for (fc_j = fc_i + 1; fc_j < fc_npaths; fc_j++)
		{
			Cost		fc_newcost;

			fc_pathinfo = fc_pathinfoarray[fc_j];
			/* 检查冗余 */
			if (bms_overlap(fc_pathinfo->clauseids, fc_clauseidsofar))
				continue;		/* 将其视为冗余 */
			if (fc_pathinfo->preds)
			{
				bool		fc_redundant = false;

				/* 我们分别检查每个谓词子句 */
				foreach(fc_l, fc_pathinfo->preds)
				{
					Node	   *fc_np = (Node *) lfirst(fc_l);

					if (predicate_implied_by(list_make1(fc_np), fc_qualsofar, false))
					{
						fc_redundant = true;
						break;	/* 从内部foreach循环中退出 */
					}
				}
				if (fc_redundant)
					continue;
			}
			/* 初步将新路径添加到路径中，以便我们可以估算成本 */
			fc_paths = lappend(fc_paths, fc_pathinfo->path);
			fc_newcost = fc_bitmap_and_cost_est(fc_root, fc_rel, fc_paths);
			if (fc_newcost < fc_costsofar)
			{
				/* 保留新路径在路径中，更新附属变量 */
				fc_costsofar = fc_newcost;
				fc_qualsofar = list_concat(fc_qualsofar, fc_pathinfo->quals);
				fc_qualsofar = list_concat(fc_qualsofar, fc_pathinfo->preds);
				fc_clauseidsofar = bms_add_members(fc_clauseidsofar,
												fc_pathinfo->clauseids);
			}
			else
			{
				/* 拒绝新路径，将其从路径列表中移除 */
				fc_paths = list_truncate(fc_paths, list_length(fc_paths) - 1);
			}
		}

		/* 保留成本最低的AND组（或单一项） */
		if (fc_i == 0 || fc_costsofar < fc_bestcost)
		{
			fc_bestpaths = fc_paths;
			fc_bestcost = fc_costsofar;
		}

		/* 一些简单的清理（虽然我们并没有努力过猛） */
		list_free(fc_qualsofar);
	}

	if (list_length(fc_bestpaths) == 1)
		return (Path *) linitial(fc_bestpaths);	/* 不需要 AND */
	return (Path *) create_bitmap_and_path(fc_root, fc_rel, fc_bestpaths);
}

/* qsort 比较函数以按照递增的索引访问成本顺序进行排序 */
static int fc_path_usage_comparator(const void *fc_a, const void *fc_b)
{
	PathClauseUsage *fc_pa = *(PathClauseUsage *const *) fc_a;
	PathClauseUsage *fc_pb = *(PathClauseUsage *const *) fc_b;
	Cost		fc_acost;
	Cost		fc_bcost;
	Selectivity fc_aselec;
	Selectivity fc_bselec;

	cost_bitmap_tree_node(fc_pa->path, &fc_acost, &fc_aselec);
	cost_bitmap_tree_node(fc_pb->path, &fc_bcost, &fc_bselec);

	/*
	 * 如果成本相同，按选择性排序。
	 */
	if (fc_acost < fc_bcost)
		return -1;
	if (fc_acost > fc_bcost)
		return 1;

	if (fc_aselec < fc_bselec)
		return -1;
	if (fc_aselec > fc_bselec)
		return 1;

	return 0;
}

/*
 * 估计使用单个索引路径实际执行位图扫描的成本
 * （这可能是 BitmapAnd 或 BitmapOr 节点）。
 */
static Cost fc_bitmap_scan_cost_est(PlannerInfo *fc_root, RelOptInfo *fc_rel, Path *fc_ipath)
{
	BitmapHeapPath fc_bpath;

	/* 设置一个虚拟的 BitmapHeapPath */
	fc_bpath.path.type = T_BitmapHeapPath;
	fc_bpath.path.pathtype = T_BitmapHeapScan;
	fc_bpath.path.parent = fc_rel;
	fc_bpath.path.pathtarget = fc_rel->reltarget;
	fc_bpath.path.param_info = fc_ipath->param_info;
	fc_bpath.path.pathkeys = NIL;
	fc_bpath.bitmapqual = fc_ipath;

	/*
	 * 检查临时路径的成本，不考虑并行性。
	 * 并行位图堆路径将在后期考虑。
	 */
	fc_bpath.path.parallel_workers = 0;

	/* 现在我们可以进行 cost_bitmap_heap_scan */
	cost_bitmap_heap_scan(&fc_bpath.path, fc_root, fc_rel,
						  fc_bpath.path.param_info,
						  fc_ipath,
						  get_loop_count(fc_root, fc_rel->relid,
										 PATH_REQ_OUTER(fc_ipath)));

	return fc_bpath.path.total_cost;
}

/*
 * 估计使用给定输入实际执行 BitmapAnd 扫描的成本。
 */
static Cost fc_bitmap_and_cost_est(PlannerInfo *fc_root, RelOptInfo *fc_rel, List *fc_paths)
{
	BitmapAndPath *fc_apath;

	/*
	 * 不妨在这里构建一个真实的 BitmapAndPath，因为工作稍微
	 * 有些复杂，不值得为了节省一个 palloc 而重复。
	 */
	fc_apath = create_bitmap_and_path(fc_root, fc_rel, fc_paths);

	return fc_bitmap_scan_cost_est(fc_root, fc_rel, (Path *) fc_apath);
}


/*
 * classify_index_clause_usage
 *		构造一个 PathClauseUsage 结构，描述给定索引扫描路径使用的 WHERE 子句和
 *		索引谓词子句。
 *		如果两个子句相等，我们认为它们是相同的。
 *
 * 在某个时刻，我们可能会想把这些信息迁移到 Path 数据
 * 结构中，但目前只在 choose_bitmap_and() 中需要它。
 *
 * *clauselist 会根据需要使用和扩展，以识别在连续调用中看到的所有不同
 * 子句。调用者必须在第一次调用时将其初始化为 NIL。
 */
static PathClauseUsage *
fc_classify_index_clause_usage(Path *fc_path, List **fc_clauselist)
{
	PathClauseUsage *fc_result;
	Bitmapset  *fc_clauseids;
	ListCell   *fc_lc;

	fc_result = (PathClauseUsage *) palloc(sizeof(PathClauseUsage));
	fc_result->path = fc_path;

	/* 递归查找路径使用的 quals 和 preds */
	fc_result->quals = NIL;
	fc_result->preds = NIL;
	fc_find_indexpath_quals(fc_path, &fc_result->quals, &fc_result->preds);

	/*
	 * 一些机器生成的查询具有异常数量的 qual 子句。
	 * 为了避免在这个初步分类步骤中出现 O(N^2) 的行为，
	 * 我们希望限制可以累积到 *clauselist 中的条目数量。
	 * 将所有具有超过 100 个 quals + preds 的路径视为无法分类，
	 * 这将导致调用代码将其视为与所有其他路径不同。
	 */
	if (list_length(fc_result->quals) + list_length(fc_result->preds) > 100)
	{
		fc_result->clauseids = NULL;
		fc_result->unclassifiable = true;
		return fc_result;
	}

	/* 构建表示 quals 和 preds 的 bitmapset */
	fc_clauseids = NULL;
	foreach(fc_lc, fc_result->quals)
	{
		Node	   *fc_node = (Node *) lfirst(fc_lc);

		fc_clauseids = bms_add_member(fc_clauseids,
								   fc_find_list_position(fc_node, fc_clauselist));
	}
	foreach(fc_lc, fc_result->preds)
	{
		Node	   *fc_node = (Node *) lfirst(fc_lc);

		fc_clauseids = bms_add_member(fc_clauseids,
								   fc_find_list_position(fc_node, fc_clauselist));
	}
	fc_result->clauseids = fc_clauseids;
	fc_result->unclassifiable = false;

	return fc_result;
}


/*
 * find_indexpath_quals
 *
 * 给定一个普通或位图索引扫描的 Path 结构，提取该路径中使用的所有索引子句和
 * 索引谓词条件的列表。这些将附加到 *quals 和 *preds 的初始内容（因此
 * 调用者应该将其初始化为 NIL）。
 *
 * 请注意，我们并不是试图生成 Path 的 AND/OR
 * 语义的准确表示，而只是找出所有使用的基本条件。
 *
 * 结果列表包含指向路径中使用的表达式的指针，
 * 但所有列表单元都是新建的，因此可以安全地破坏性
 * 修改列表（例如，通过与其他列表连接）。
 */
static void fc_find_indexpath_quals(Path *fc_bitmapqual, List **fc_quals, List **fc_preds)
{
	if (IsA(fc_bitmapqual, BitmapAndPath))
	{
		BitmapAndPath *fc_apath = (BitmapAndPath *) fc_bitmapqual;
		ListCell   *fc_l;

		foreach(fc_l, fc_apath->bitmapquals)
		{
			fc_find_indexpath_quals((Path *) lfirst(fc_l), fc_quals, fc_preds);
		}
	}
	else if (IsA(fc_bitmapqual, BitmapOrPath))
	{
		BitmapOrPath *fc_opath = (BitmapOrPath *) fc_bitmapqual;
		ListCell   *fc_l;

		foreach(fc_l, fc_opath->bitmapquals)
		{
			fc_find_indexpath_quals((Path *) lfirst(fc_l), fc_quals, fc_preds);
		}
	}
	else if (IsA(fc_bitmapqual, IndexPath))
	{
		IndexPath  *fc_ipath = (IndexPath *) fc_bitmapqual;
		ListCell   *fc_l;

		foreach(fc_l, fc_ipath->indexclauses)
		{
			IndexClause *fc_iclause = (IndexClause *) lfirst(fc_l);

			*fc_quals = lappend(*fc_quals, fc_iclause->rinfo->clause);
		}
		*fc_preds = list_concat(*fc_preds, fc_ipath->indexinfo->indpred);
	}
	else
		elog(ERROR, "unrecognized node type: %d", nodeTag(fc_bitmapqual));
}


/*
 * find_list_position
 *		返回给定节点在给定节点列表中的位置（从 0 开始计数）。
 *		如果它与任何现有列表成员不相等，则将其添加到末尾，并返回该位置。
 */
static int fc_find_list_position(Node *fc_node, List **fc_nodelist)
{
	int			fc_i;
	ListCell   *fc_lc;

	fc_i = 0;
	foreach(fc_lc, *fc_nodelist)
	{
		Node	   *fc_oldnode = (Node *) lfirst(fc_lc);

		if (equal(fc_node, fc_oldnode))
			return fc_i;
		fc_i++;
	}

	*fc_nodelist = lappend(*fc_nodelist, fc_node);

	return fc_i;
}


/*
 * check_index_only
 *		确定此索引是否可以进行索引仅扫描。
 */
static bool fc_check_index_only(RelOptInfo *fc_rel, IndexOptInfo *fc_index)
{
	bool		fc_result;
	Bitmapset  *fc_attrs_used = NULL;
	Bitmapset  *fc_index_canreturn_attrs = NULL;
	ListCell   *fc_lc;
	int			fc_i;

	/* 必须启用索引仅扫描 */
	if (!enable_indexonlyscan)
		return false;

	/*
	 * 检查从索引中是否可以获得关系所需的所有属性。
	 */

	/*
	 * 首先，识别联接或最终输出所需的所有属性。
	 * 注意：我们必须查看 rel 的 targetlist，而不是 attr_needed 数据，
	 * 因为 attr_needed 不会为继承子关系计算。
	 */
	pull_varattnos((Node *) fc_rel->reltarget->exprs, fc_rel->relid, &fc_attrs_used);

	/*
	 * 添加所有由限制子句使用的属性；但只考虑
	 * 那些不被索引谓词暗示的子句，因为那些被暗示的子句
	 * 在计划中不需要显式检查。
	 *
	 * 注意：仅在索引 quals 中使用的属性在运行时也不会需要，
	 * 如果我们确信索引不是有损的。然而，准确考虑这一点会很复杂，
	 * 而且在大多数情况下并不重要，因为我们会得出这些属性无论如何
	 * 都可以从索引中获得的结论。
	 */
	foreach(fc_lc, fc_index->indrestrictinfo)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);

		pull_varattnos((Node *) fc_rinfo->clause, fc_rel->relid, &fc_attrs_used);
	}

	/*
	 * 构造一个位图集，该位图集包含索引可以在
	 * 索引仅扫描中返回的列。
	 */
	for (fc_i = 0; fc_i < fc_index->ncolumns; fc_i++)
	{
		int			fc_attno = fc_index->indexkeys[fc_i];

		/*
		 * 暂时，我们只是忽略索引表达式。稍后
		 * 可以考虑对其进行一些处理。
		 */
		if (fc_attno == 0)
			continue;

		if (fc_index->canreturn[fc_i])
			fc_index_canreturn_attrs =
				bms_add_member(fc_index_canreturn_attrs,
							   fc_attno - FirstLowInvalidHeapAttributeNumber);
	}

	/* 我们是否拥有所有必要的属性？ */
	fc_result = bms_is_subset(fc_attrs_used, fc_index_canreturn_attrs);

	bms_free(fc_attrs_used);
	bms_free(fc_index_canreturn_attrs);

	return fc_result;
}


/*
 * get_loop_count
 *		选择用于对具有给定外部 relids 的参数化路径进行成本估算的循环计数估计。
 *
 * 由于我们在开始生成连接关系之前就产生了参数化路径，因此无法准确预测参数化路径将迭代多少次；我们不知道外部嵌套循环的关系大小。然而，我们应该尝试在对路径进行成本估算时以某种方式考虑多次迭代。此处体现的启发式方法是使用路径所需的连接子句中最小的其他基本关系的行数统计。（我们可以考虑最大的那个，但那似乎过于乐观。）对于单一其他关系的情况，这当然是正确的答案，而对于多路连接情况，这似乎是一个合理的零阶近似。
 *
 * 此外，我们检查每个连接子句的另一边是否在某个半连接的内部，而当前关系位于其外部。如果是这样，使用参数化路径的唯一方式是 semijoin 右侧已被唯一化，因此我们应该使用唯一右侧行的数量，而不是使用关系的原始行数统计。
 *
 * 注意：为了使其有效，allpaths.c 必须在开始计算路径之前，或至少在调用 create_index_paths() 之前，确定所有基本关系的大小估算。
 */
static double get_loop_count(PlannerInfo *fc_root, Index fc_cur_relid, Relids fc_outer_relids)
{
	double		fc_result;
	int			fc_outer_relid;

	/* 对于非参数化路径，快速返回 1.0 */
	if (fc_outer_relids == NULL)
		return 1.0;

	fc_result = 0.0;
	fc_outer_relid = -1;
	while ((fc_outer_relid = bms_next_member(fc_outer_relids, fc_outer_relid)) >= 0)
	{
		RelOptInfo *fc_outer_rel;
		double		fc_rowcount;

		/* 幻觉：忽略虚假的 relid 索引 */
		if (fc_outer_relid >= fc_root->simple_rel_array_size)
			continue;
		fc_outer_rel = fc_root->simple_rel_array[fc_outer_relid];
		if (fc_outer_rel == NULL)
			continue;
		Assert(fc_outer_rel->relid == fc_outer_relid);	/* 对数组进行完整性检查 */

		/* 其他关系可以被证明为空，如果是这样则忽略 */
		if (IS_DUMMY_REL(fc_outer_rel))
			continue;

		/* 否则，rel 的行估算现在应该是有效的 */
		Assert(fc_outer_rel->rows > 0);

		/* 检查 rel 是否位于任何半连接的内部 */
		fc_rowcount = fc_adjust_rowcount_for_semijoins(fc_root,
												 fc_cur_relid,
												 fc_outer_relid,
												 fc_outer_rel->rows);

		/* 记住外部关系中最小的行数估计 */
		if (fc_result == 0.0 || fc_result > fc_rowcount)
			fc_result = fc_rowcount;
	}
	/* 如果没有找到有效的关系，则返回1.0（不应该发生） */
	return (fc_result > 0.0) ? fc_result : 1.0;
}

/*
 * 检查 outer_relid 是否在任何 semijoin 的内部，而 cur_relid 在其外部。
 * 如果是，则用 semijoin 右侧的唯一行的估计数量替换行数（假设这更小，虽然可能不是）。
 * 这个估计是粗略的，但这是我们在此阶段能做到的最好结果。
 */
static double fc_adjust_rowcount_for_semijoins(PlannerInfo *fc_root,
							  Index fc_cur_relid,
							  Index fc_outer_relid,
							  double fc_rowcount)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_root->join_info_list)
	{
		SpecialJoinInfo *fc_sjinfo = (SpecialJoinInfo *) lfirst(fc_lc);

		if (fc_sjinfo->jointype == JOIN_SEMI &&
			bms_is_member(fc_cur_relid, fc_sjinfo->syn_lefthand) &&
			bms_is_member(fc_outer_relid, fc_sjinfo->syn_righthand))
		{
			/* 估计唯一化行的数量 */
			double		fc_nraw;
			double		fc_nunique;

			fc_nraw = fc_approximate_joinrel_size(fc_root, fc_sjinfo->syn_righthand);
			fc_nunique = estimate_num_groups(fc_root,
										  fc_sjinfo->semi_rhs_exprs,
										  fc_nraw,
										  NULL,
										  NULL);
			if (fc_rowcount > fc_nunique)
				fc_rowcount = fc_nunique;
		}
	}
	return fc_rowcount;
}

/*
 * 对 joinrel 的大小做一个近似估计。
 *
 * 在这一点上我们没有足够的信息来获得一个好的估计，因此我们
 * 只是将基础关系的大小相乘。幸运的是，这对于 semijoin 的右侧
 * 有单个关系的最常见情况来说依然是正确的答案。此外，estimate_num_groups()
 * 仅对它的 input_rows 参数有弱依赖（它基本上将其用作限制）。
 * 所以，即使对右侧的原始大小有严重的高估，我们也可能得到一个相当
 * 可观的最终结果。
 */
static double fc_approximate_joinrel_size(PlannerInfo *fc_root, Relids fc_relids)
{
	double		fc_rowcount = 1.0;
	int			fc_relid;

	fc_relid = -1;
	while ((fc_relid = bms_next_member(fc_relids, fc_relid)) >= 0)
	{
		RelOptInfo *fc_rel;

		/* 幻觉：忽略虚假的 relid 索引 */
		if (fc_relid >= fc_root->simple_rel_array_size)
			continue;
		fc_rel = fc_root->simple_rel_array[fc_relid];
		if (fc_rel == NULL)
			continue;
		Assert(fc_rel->relid == fc_relid);	/* 对数组进行完整性检查 */

		/* 如果关系可以被证明为空，则忽略 */
		if (IS_DUMMY_REL(fc_rel))
			continue;

		/* 否则，rel 的行估算现在应该是有效的 */
		Assert(fc_rel->rows > 0);

		/* 累积乘积 */
		fc_rowcount *= fc_rel->rows;
	}
	return fc_rowcount;
}


/****************************************************************************
 *				---- 检查查询子句的例程 ----
 ****************************************************************************/

/*
 * match_restriction_clauses_to_index
 *	  确定与索引匹配的关系的限制子句。
 *	  匹配的子句将被添加到 *clauseset。
 */
static void fc_match_restriction_clauses_to_index(PlannerInfo *fc_root,
								   IndexOptInfo *fc_index,
								   IndexClauseSet *fc_clauseset)
{
	/* 我们可以忽略被索引谓词隐含的子句 */
	fc_match_clauses_to_index(fc_root, fc_index->indrestrictinfo, fc_index, fc_clauseset);
}

/*
 * match_join_clauses_to_index
 *	  确定与索引匹配的关系的连接子句。
 *	  匹配的子句将被添加到 *clauseset。
 *	  此外，还将任何潜在可用的连接 OR 子句添加到 *joinorclauses。
 */
static void fc_match_join_clauses_to_index(PlannerInfo *fc_root,
							RelOptInfo *fc_rel, IndexOptInfo *fc_index,
							IndexClauseSet *fc_clauseset,
							List **fc_joinorclauses)
{
	ListCell   *fc_lc;

	/* 扫描关系的连接子句 */
	foreach(fc_lc, fc_rel->joininfo)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);

		/* 检查子句是否可以移动到该关系 */
		if (!join_clause_is_movable_to(fc_rinfo, fc_rel))
			continue;

		/* 潜在可用，因此查看它是否匹配索引或是 OR */
		if (restriction_is_or_clause(fc_rinfo))
			*fc_joinorclauses = lappend(*fc_joinorclauses, fc_rinfo);
		else
			fc_match_clause_to_index(fc_root, fc_rinfo, fc_index, fc_clauseset);
	}
}

/*
 * match_eclass_clauses_to_index
 *	  确定与索引匹配的 EquivalenceClass 连接子句。
 *	  匹配的子句将被添加到 *clauseset。
 */
static void fc_match_eclass_clauses_to_index(PlannerInfo *fc_root, IndexOptInfo *fc_index,
							  IndexClauseSet *fc_clauseset)
{
	int			fc_indexcol;

	/* 如果关系不在任何这样的 EC 中则无需工作 */
	if (!fc_index->rel->has_eclass_joins)
		return;

	for (fc_indexcol = 0; fc_indexcol < fc_index->nkeycolumns; fc_indexcol++)
	{
		ec_member_matches_arg fc_arg;
		List	   *fc_clauses;

		/* 生成子句，跳过任何连接到 lateral_referencers 的子句 */
		fc_arg.index = fc_index;
		fc_arg.indexcol = fc_indexcol;
		fc_clauses = generate_implied_equalities_for_column(fc_root,
														 fc_index->rel,
														 fc_ec_member_matches_indexcol,
														 (void *) &fc_arg,
														 fc_index->rel->lateral_referencers);

		/*
		 * 我们必须检查结果是否确实匹配索引，
		 * 因为对于非 btree 索引，EC 的相等运算符可能不在
		 * 索引操作类中（参见 ec_member_matches_indexcol）。
 */
		fc_match_clauses_to_index(fc_root, fc_clauses, fc_index, fc_clauseset);
	}
}

/*
 * match_clauses_to_index
 *	  对列表中的每个子句执行 match_clause_to_index()。
 *	  匹配的子句将被添加到 *clauseset。
 */
static void fc_match_clauses_to_index(PlannerInfo *fc_root,
					   List *fc_clauses,
					   IndexOptInfo *fc_index,
					   IndexClauseSet *fc_clauseset)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_clauses)
	{
		RestrictInfo *fc_rinfo = lfirst_node(RestrictInfo, fc_lc);

		fc_match_clause_to_index(fc_root, fc_rinfo, fc_index, fc_clauseset);
	}
}

/*
 * match_clause_to_index
 *	  测试一个 qual 子句是否可以与索引一起使用。
 *
 * 如果子句是可用的，向 *clauseset 的适当列表中添加一个
 * IndexClause 条目。(*clauseset 必须在第一次调用之前初始化为零。)
 *
 * 注意：在某些情况下，我们可能会发现来自多个地方相同的 RestrictInfos。
 * 通过拒绝重复添加子句来防御冗余输出（指针相等性应该是一个
 * 足够好的检查）。
 *
 * 注意：一个定义不佳的索引可能具有多个匹配的列。我们总是选择
 * 第一个匹配；这避免了通过多次使用相同的子句与不同的索引
 * 列出现对索引选择性的夸大理解的情况。
 */
static void fc_match_clause_to_index(PlannerInfo *fc_root,
					  RestrictInfo *fc_rinfo,
					  IndexOptInfo *fc_index,
					  IndexClauseSet *fc_clauseset)
{
	int			fc_indexcol;

	/*
	 * 永远不要将伪常量与索引匹配。（通常来说，匹配不可能发生，
	 * 因为伪常量子句不能包含 Var，但如果有人在常数上生成了
	 * 表达式索引呢？使用部分索引完全合理。）
	 */
	if (fc_rinfo->pseudoconstant)
		return;

	/*
	 * 如果子句不能用作 indexqual，因为它必须等到某些
	 * 安全级别较低的限制子句之后再执行，则拒绝它。
	 */
	if (!restriction_is_securely_promotable(fc_rinfo, fc_index->rel))
		return;

	/* 好吧，检查每个索引键列是否匹配 */
	for (fc_indexcol = 0; fc_indexcol < fc_index->nkeycolumns; fc_indexcol++)
	{
		IndexClause *fc_iclause;
		ListCell   *fc_lc;

		/* 忽略重复项 */
		foreach(fc_lc, fc_clauseset->indexclauses[fc_indexcol])
		{
			IndexClause *fc_iclause = (IndexClause *) lfirst(fc_lc);

			if (fc_iclause->rinfo == fc_rinfo)
				return;
		}

		/* 好吧，尝试将子句与索引列匹配 */
		fc_iclause = fc_match_clause_to_indexcol(fc_root,
										   fc_rinfo,
										   fc_indexcol,
										   fc_index);
		if (fc_iclause)
		{
			/* 成功，因此记录下来 */
			fc_clauseset->indexclauses[fc_indexcol] =
				lappend(fc_clauseset->indexclauses[fc_indexcol], fc_iclause);
			fc_clauseset->nonempty = true;
			return;
		}
	}
}


/*
 * match_clause_to_indexcol()
 *	  确定限制子句是否匹配索引的列，如果匹配，则构建描述详细信息的 IndexClause 节点。
 *
 *	  要正常匹配索引，运算符子句：
 *
 *	  (1)  必须是 (indexkey op const) 或 (const op indexkey) 的形式；
 *		   和
 *	  (2)  必须包含在该列的索引运算符族中的运算符；并且
 *	  (3)  如果相关性相关，必须匹配索引的排序规则。
 *
 *	  我们对“const”的定义非常宽松：我们允许不涉及波动函数或索引关系的 Var 的任何东西。
 *	  特别是，属于查询的其他关系的 Vars 在这里被接受，因为该形式的子句可以用于参数化索引扫描。
 *	  更高代码层次负责适当管理限制和连接子句。
 *
 *	  注意：我们确实需要检查子句的“const”一侧是否有索引关系的 Vars，因为像 (a.f1 OP (b.f2 OP a.f3)) 这样的子句
 *	  不能通过 a.f1 的参数化索引扫描进行处理，而像 (a.f1 OP (b.f2 OP c.f3)) 这样的子句可以。
 *
 *	  目前，执行器只能处理左侧有 indexkey 的 indexquals，因此我们只能使用右侧有 indexkey 的子句
 *	  如果我们可以调换子句使得关键字在左侧。我们通过生成一个具有正确调换的 opclause 作为派生 indexqual 的 IndexClause 来处理这个问题。
 *
 *	  如果索引具有排序规则，则子句必须具有相同的排序规则。
 *	  对于没有排序规则的索引，我们假设这并不重要；这是
 *	  在 "hstore ? text" 这样的情况下是必要的，其中 hstore 的运算符
 *	  不在乎排序规则，但子句会因为文本参数而被标记带有排序规则。 (这个逻辑
 *	  体现在宏 IndexCollMatchesExprColl 中。)
 *
 *	  也可以将 RowCompareExpr 子句匹配到索引（但
 *	  目前，只有 btree 索引处理此情况）。
 *
 *	  也可以将 ScalarArrayOpExpr 子句匹配到索引，当
 *	  子句的形式为 "indexkey op ANY (arrayconst)" 时。
 *
 *	  对于布尔索引，也可以将子句直接匹配到 indexkey；或者子句为 (NOT indexkey)。
 *
 *	  最后但并非最不重要的是，一些运算符和函数可以处理
 *	  从不是可索引的子句中派生（通常是有损的）indexquals。
 *	  如果我们看到 OpExpr 或 FuncExpr 的任何操作数与索引键匹配，并且函数有附加的规划器支持函数，
 *	  我们将调用支持函数查看是否可以构建这样的 indexqual。
 *
 * 'rinfo' 是要测试的子句（作为 RestrictInfo 节点）。
 * 'indexcol' 是 'index' 的列号（从 0 开始计数）。
 * 'index' 是感兴趣的索引。
 *
 * 如果子句可以与此索引键一起使用，则返回一个 IndexClause，
 * 否则返回 NULL。
 *
 * 注意：如果子句是 OR 或 AND 子句，返回 NULL；处理这些的责任在于更高级别的例程。
 */
static IndexClause * fc_match_clause_to_indexcol(PlannerInfo *fc_root,
						 RestrictInfo *fc_rinfo,
						 int fc_indexcol,
						 IndexOptInfo *fc_index)
{
	IndexClause *fc_iclause;
	Expr	   *fc_clause = fc_rinfo->clause;
	Oid			fc_opfamily;

	Assert(fc_indexcol < fc_index->nkeycolumns);

	/*
	 * 历史上，这段代码处理了 NULL 子句。可能
	 * 现在已经不可能了，但我们还是继续处理吧。
	 */
	if (fc_clause == NULL)
		return NULL;

	/* 首先检查布尔索引情况。 */
	fc_opfamily = fc_index->opfamily[fc_indexcol];
	if (IsBooleanOpfamily(fc_opfamily))
	{
		fc_iclause = fc_match_boolean_index_clause(fc_root, fc_rinfo, fc_indexcol, fc_index);
		if (fc_iclause)
			return fc_iclause;
	}

	/*
	 * 子句必须是 opclause、funcclause、ScalarArrayOpExpr 或
	 * RowCompareExpr。或者，如果索引支持，我们可以处理 IS
	 * NULL/NOT NULL 子句。
	 */
	if (IsA(fc_clause, OpExpr))
	{
		return fc_match_opclause_to_indexcol(fc_root, fc_rinfo, fc_indexcol, fc_index);
	}
	else if (IsA(fc_clause, FuncExpr))
	{
		return fc_match_funcclause_to_indexcol(fc_root, fc_rinfo, fc_indexcol, fc_index);
	}
	else if (IsA(fc_clause, ScalarArrayOpExpr))
	{
		return fc_match_saopclause_to_indexcol(fc_root, fc_rinfo, fc_indexcol, fc_index);
	}
	else if (IsA(fc_clause, RowCompareExpr))
	{
		return fc_match_rowcompare_to_indexcol(fc_root, fc_rinfo, fc_indexcol, fc_index);
	}
	else if (fc_index->amsearchnulls && IsA(fc_clause, NullTest))
	{
		NullTest   *fc_nt = (NullTest *) fc_clause;

		if (!fc_nt->argisrow &&
			match_index_to_operand((Node *) fc_nt->arg, fc_indexcol, fc_index))
		{
			fc_iclause = makeNode(IndexClause);
			fc_iclause->rinfo = fc_rinfo;
			fc_iclause->indexquals = list_make1(fc_rinfo);
			fc_iclause->lossy = false;
			fc_iclause->indexcol = fc_indexcol;
			fc_iclause->indexcols = NIL;
			return fc_iclause;
		}
	}

	return NULL;
}

/*
 * match_boolean_index_clause
 *	  识别可以匹配布尔索引的限制子句。
 *
 * 这里的想法是，对于支持 BooleanEqualOperator 的布尔列的索引，
 * 我们可以将对 indexkey 的普通引用转换为 "indexkey = true"，或将 "NOT indexkey" 转换为 "indexkey = false"，等等，
 * 以使该表达式可通过索引的 "=" 运算符进行索引。
 * 自 Postgres 8.1 以来，我们必须这样做，因为常量简化会做反转转换；没有这段代码，就无法使用
 * 这样的索引。
 *
 * 仅在 IsBooleanOpfamily() 识别索引的运算符族时调用此方法。
 * 我们检查子句是否匹配索引的键，如果匹配，则构建适当的 IndexClause。
 */
static IndexClause * fc_match_boolean_index_clause(PlannerInfo *fc_root,
						   RestrictInfo *fc_rinfo,
						   int fc_indexcol,
						   IndexOptInfo *fc_index)
{
	Node	   *fc_clause = (Node *) fc_rinfo->clause;
	Expr	   *fc_op = NULL;

	/* 直接匹配？ */
	if (match_index_to_operand(fc_clause, fc_indexcol, fc_index))
	{
		/* 转换为 indexkey = TRUE */
		fc_op = make_opclause(BooleanEqualOperator, BOOLOID, false,
						   (Expr *) fc_clause,
						   (Expr *) makeBoolConst(true, false),
						   InvalidOid, InvalidOid);
	}
	/* NOT 子句？ */
	else if (is_notclause(fc_clause))
	{
		Node	   *fc_arg = (Node *) get_notclausearg((Expr *) fc_clause);

		if (match_index_to_operand(fc_arg, fc_indexcol, fc_index))
		{
			/* 转换为 indexkey = FALSE */
			fc_op = make_opclause(BooleanEqualOperator, BOOLOID, false,
							   (Expr *) fc_arg,
							   (Expr *) makeBoolConst(false, false),
							   InvalidOid, InvalidOid);
		}
	}

	/*
	 * 由于我们只考虑 WHERE 的顶级子句，我们可以将
	 * indexkey IS TRUE 和 indexkey IS FALSE 转换为索引搜索。不同的
	 * NULL 含义并不重要。
	 */
	else if (fc_clause && IsA(fc_clause, BooleanTest))
	{
		BooleanTest *fc_btest = (BooleanTest *) fc_clause;
		Node	   *fc_arg = (Node *) fc_btest->arg;

		if (fc_btest->booltesttype == IS_TRUE &&
			match_index_to_operand(fc_arg, fc_indexcol, fc_index))
		{
			/* 转换为 indexkey = TRUE */
			fc_op = make_opclause(BooleanEqualOperator, BOOLOID, false,
							   (Expr *) fc_arg,
							   (Expr *) makeBoolConst(true, false),
							   InvalidOid, InvalidOid);
		}
		else if (fc_btest->booltesttype == IS_FALSE &&
				 match_index_to_operand(fc_arg, fc_indexcol, fc_index))
		{
			/* 转换为 indexkey = FALSE */
			fc_op = make_opclause(BooleanEqualOperator, BOOLOID, false,
							   (Expr *) fc_arg,
							   (Expr *) makeBoolConst(false, false),
							   InvalidOid, InvalidOid);
		}
	}

	/*
	 * 如果我们成功地从给定的 qual 制作了一个操作符子句，我们必须
	 * 将其包装在 IndexClause 中。这不会丢失信息。
	 */
	if (fc_op)
	{
		IndexClause *fc_iclause = makeNode(IndexClause);

		fc_iclause->rinfo = fc_rinfo;
		fc_iclause->indexquals = list_make1(make_simple_restrictinfo(fc_root, fc_op));
		fc_iclause->lossy = false;
		fc_iclause->indexcol = fc_indexcol;
		fc_iclause->indexcols = NIL;
		return fc_iclause;
	}

	return NULL;
}

/*
 * match_opclause_to_indexcol()
 *	  处理 match_clause_to_indexcol() 中的 OpExpr 情况，
 *	  有关注释请参见。
 */
static IndexClause * fc_match_opclause_to_indexcol(PlannerInfo *fc_root,
						   RestrictInfo *fc_rinfo,
						   int fc_indexcol,
						   IndexOptInfo *fc_index)
{
	IndexClause *fc_iclause;
	OpExpr	   *fc_clause = (OpExpr *) fc_rinfo->clause;
	Node	   *fc_leftop,
			   *fc_rightop;
	Oid			fc_expr_op;
	Oid			fc_expr_coll;
	Index		fc_index_relid;
	Oid			fc_opfamily;
	Oid			fc_idxcollation;

	/*
	 * 只有二元操作符需要应用。(理论上，规划器支持
	 * 函数可以处理一元操作符，但似乎不值得检查)
	 */
	if (list_length(fc_clause->args) != 2)
		return NULL;

	fc_leftop = (Node *) linitial(fc_clause->args);
	fc_rightop = (Node *) lsecond(fc_clause->args);
	fc_expr_op = fc_clause->opno;
	fc_expr_coll = fc_clause->inputcollid;

	fc_index_relid = fc_index->rel->relid;
	fc_opfamily = fc_index->opfamily[fc_indexcol];
	fc_idxcollation = fc_index->indexcollations[fc_indexcol];

	/*
	 * 检查以下形式的子句：(indexkey operator constant) 或
	 * (constant operator indexkey)。有关 const-ness 的说明请参见 
	 * match_clause_to_indexcol 的说明。
	 *
	 * 请注意，我们不会询问支持函数有关不具备这些形式的子句。 
	 * 再次强调，原则上可能可以做一些事情，但似乎不值得检查。
	 */
	if (match_index_to_operand(fc_leftop, fc_indexcol, fc_index) &&
		!bms_is_member(fc_index_relid, fc_rinfo->right_relids) &&
		!contain_volatile_functions(fc_rightop))
	{
		if (IndexCollMatchesExprColl(fc_idxcollation, fc_expr_coll) &&
			op_in_opfamily(fc_expr_op, fc_opfamily))
		{
			fc_iclause = makeNode(IndexClause);
			fc_iclause->rinfo = fc_rinfo;
			fc_iclause->indexquals = list_make1(fc_rinfo);
			fc_iclause->lossy = false;
			fc_iclause->indexcol = fc_indexcol;
			fc_iclause->indexcols = NIL;
			return fc_iclause;
		}

		/*
		 * 如果我们没有找到索引的 opfamily 的成员，则尝试
		 * 操作符的基础函数的支持函数。
		 */
		set_opfuncid(fc_clause);	/* 确保我们有 opfuncid */
		return get_index_clause_from_support(fc_root,
											 fc_rinfo,
											 fc_clause->opfuncid,
											 0, /* indexarg 在左侧 */
											 fc_indexcol,
											 fc_index);
	}

	if (match_index_to_operand(fc_rightop, fc_indexcol, fc_index) &&
		!bms_is_member(fc_index_relid, fc_rinfo->left_relids) &&
		!contain_volatile_functions(fc_leftop))
	{
		if (IndexCollMatchesExprColl(fc_idxcollation, fc_expr_coll))
		{
			Oid			fc_comm_op = get_commutator(fc_expr_op);

			if (OidIsValid(fc_comm_op) &&
				op_in_opfamily(fc_comm_op, fc_opfamily))
			{
				RestrictInfo *fc_commrinfo;

				/* 构建一个重新排列的 OpExpr 和 RestrictInfo */
				fc_commrinfo = commute_restrictinfo(fc_rinfo, fc_comm_op);

				/* 创建一个 IndexClause，显示这是一个派生的 qual */
				fc_iclause = makeNode(IndexClause);
				fc_iclause->rinfo = fc_rinfo;
				fc_iclause->indexquals = list_make1(fc_commrinfo);
				fc_iclause->lossy = false;
				fc_iclause->indexcol = fc_indexcol;
				fc_iclause->indexcols = NIL;
				return fc_iclause;
			}
		}

		/*
		 * 如果我们没有找到索引的 opfamily 的成员，则尝试
		 * 操作符的基础函数的支持函数。
		 */
		set_opfuncid(fc_clause);	/* 确保我们有 opfuncid */
		return get_index_clause_from_support(fc_root,
											 fc_rinfo,
											 fc_clause->opfuncid,
											 1, /* indexarg 在右侧 */
											 fc_indexcol,
											 fc_index);
	}

	return NULL;
}

/*
 * match_funcclause_to_indexcol()
 *	  处理 match_clause_to_indexcol() 中的 FuncExpr 情况，
 *	  有关注释请参见。
 */
static IndexClause * fc_match_funcclause_to_indexcol(PlannerInfo *fc_root,
							 RestrictInfo *fc_rinfo,
							 int fc_indexcol,
							 IndexOptInfo *fc_index)
{
	FuncExpr   *fc_clause = (FuncExpr *) fc_rinfo->clause;
	int			fc_indexarg;
	ListCell   *fc_lc;

	/*
	 * 我们对函数子句没有内置的智能，但如果有
	 * 规划器支持函数，它可能能够做一些事情。为了减少
	 * 浪费的规划周期，只有在至少一个参数与目标索引列匹配时
	 * 才调用支持函数。
	 *
	 * 请注意，我们并不坚持其他参数为伪常量；
	 * 支持函数必须检查这一点。这是为了允许只需
	 * 将一些其他参数包含在 indexqual 中的情况。
	 */
	fc_indexarg = 0;
	foreach(fc_lc, fc_clause->args)
	{
		Node	   *fc_op = (Node *) lfirst(fc_lc);

		if (match_index_to_operand(fc_op, fc_indexcol, fc_index))
		{
			return get_index_clause_from_support(fc_root,
												 fc_rinfo,
												 fc_clause->funcid,
												 fc_indexarg,
												 fc_indexcol,
												 fc_index);
		}

		fc_indexarg++;
	}

	return NULL;
}

/*
 * get_index_clause_from_support()
 *		如果该函数有一个规划器支持函数，尝试
 *		使用支持函数创建的 indexquals 构造一个 IndexClause。
 */
static IndexClause * get_index_clause_from_support(PlannerInfo *fc_root,
							  RestrictInfo *fc_rinfo,
							  Oid fc_funcid,
							  int fc_indexarg,
							  int fc_indexcol,
							  IndexOptInfo *fc_index)
{
	Oid			fc_prosupport = get_func_support(fc_funcid);
	SupportRequestIndexCondition fc_req;
	List	   *fc_sresult;

	if (!OidIsValid(fc_prosupport))
		return NULL;

	fc_req.type = T_SupportRequestIndexCondition;
	fc_req.root = fc_root;
	fc_req.funcid = fc_funcid;
	fc_req.node = (Node *) fc_rinfo->clause;
	fc_req.indexarg = fc_indexarg;
	fc_req.index = fc_index;
	fc_req.indexcol = fc_indexcol;
	fc_req.opfamily = fc_index->opfamily[fc_indexcol];
	fc_req.indexcollation = fc_index->indexcollations[fc_indexcol];

	fc_req.lossy = true;			/* 默认假设 */

	fc_sresult = (List *)
		DatumGetPointer(OidFunctionCall1(fc_prosupport,
										 PointerGetDatum(&fc_req)));

	if (fc_sresult != NIL)
	{
		IndexClause *fc_iclause = makeNode(IndexClause);
		List	   *fc_indexquals = NIL;
		ListCell   *fc_lc;

		/*
		 * 支持函数 API 说它应该只返回裸子句，
		 * 因此在这里我们必须将每一个包装在 RestrictInfo 中。
		 */
		foreach(fc_lc, fc_sresult)
		{
			Expr	   *fc_clause = (Expr *) lfirst(fc_lc);

			fc_indexquals = lappend(fc_indexquals,
								 make_simple_restrictinfo(fc_root, fc_clause));
		}

		fc_iclause->rinfo = fc_rinfo;
		fc_iclause->indexquals = fc_indexquals;
		fc_iclause->lossy = fc_req.lossy;
		fc_iclause->indexcol = fc_indexcol;
		fc_iclause->indexcols = NIL;

		return fc_iclause;
	}

	return NULL;
}

/*
 * match_saopclause_to_indexcol()
 *	  处理 match_clause_to_indexcol() 中的 ScalarArrayOpExpr 情况，
 *	  有关注释请参见。
 */
static IndexClause * fc_match_saopclause_to_indexcol(PlannerInfo *fc_root,
							 RestrictInfo *fc_rinfo,
							 int fc_indexcol,
							 IndexOptInfo *fc_index)
{
	ScalarArrayOpExpr *fc_saop = (ScalarArrayOpExpr *) fc_rinfo->clause;
	Node	   *fc_leftop,
			   *fc_rightop;
	Relids		fc_right_relids;
	Oid			fc_expr_op;
	Oid			fc_expr_coll;
	Index		fc_index_relid;
	Oid			fc_opfamily;
	Oid			fc_idxcollation;

	/* 我们只接受 ANY 子句，而不是 ALL */
	if (!fc_saop->useOr)
		return NULL;
	fc_leftop = (Node *) linitial(fc_saop->args);
	fc_rightop = (Node *) lsecond(fc_saop->args);
	fc_right_relids = pull_varnos(fc_root, fc_rightop);
	fc_expr_op = fc_saop->opno;
	fc_expr_coll = fc_saop->inputcollid;

	fc_index_relid = fc_index->rel->relid;
	fc_opfamily = fc_index->opfamily[fc_indexcol];
	fc_idxcollation = fc_index->indexcollations[fc_indexcol];

	/*
	 * 我们必须在左侧有 indexkey 和一个伪常量数组参数。
	 */
	if (match_index_to_operand(fc_leftop, fc_indexcol, fc_index) &&
		!bms_is_member(fc_index_relid, fc_right_relids) &&
		!contain_volatile_functions(fc_rightop))
	{
		if (IndexCollMatchesExprColl(fc_idxcollation, fc_expr_coll) &&
			op_in_opfamily(fc_expr_op, fc_opfamily))
		{
			IndexClause *fc_iclause = makeNode(IndexClause);

			fc_iclause->rinfo = fc_rinfo;
			fc_iclause->indexquals = list_make1(fc_rinfo);
			fc_iclause->lossy = false;
			fc_iclause->indexcol = fc_indexcol;
			fc_iclause->indexcols = NIL;
			return fc_iclause;
		}

		/*
		 * 我们目前不询问支持函数关于 ScalarArrayOpExprs 的问题，
		 * 尽管原则上我们可以这样做。
		 */
	}

	return NULL;
}

/*
 * match_rowcompare_to_indexcol()
 *	  处理 match_clause_to_indexcol() 中的 RowCompareExpr 情况，
 *	  有关注释请参见。
 *
 * 在该例程中，我们检查行比较的第一列是否
 * 与目标索引列匹配。这足以保证可以从 RowCompareExpr 
 * 构建某个索引条件 --- 剩余部分由 expand_indexqual_rowcompare() 
 * 处理。
 */
static IndexClause * fc_match_rowcompare_to_indexcol(PlannerInfo *fc_root,
							 RestrictInfo *fc_rinfo,
							 int fc_indexcol,
							 IndexOptInfo *fc_index)
{
	RowCompareExpr *fc_clause = (RowCompareExpr *) fc_rinfo->clause;
	Index		fc_index_relid;
	Oid			fc_opfamily;
	Oid			fc_idxcollation;
	Node	   *fc_leftop,
			   *fc_rightop;
	bool		fc_var_on_left;
	Oid			fc_expr_op;
	Oid			fc_expr_coll;

	/* 如果我们不处理 btree 索引，就忘掉它 */
	if (fc_index->relam != BTREE_AM_OID)
		return NULL;

	fc_index_relid = fc_index->rel->relid;
	fc_opfamily = fc_index->opfamily[fc_indexcol];
	fc_idxcollation = fc_index->indexcollations[fc_indexcol];

	/*
	 * 我们可以在 RowCompareExpr 中坚持 opfamily 
	 * 与索引列的 opfamily 相同的基础上进行匹配，但是在存在
	 * 反向排序 opfamilies 的情况下，这可能会失败：RowCompareExpr 
	 * 是否选择了正向还是反向排序系列将是一种偶然。因此只关注
	 * 操作符，如果它是索引的 opfamily 的成员则匹配（如果 
	 * indexkey 在右侧则进行换位）。我们稍后再处理
	 * 是否有任何其他操作符可以与索引匹配的问题。
	 */
	fc_leftop = (Node *) linitial(fc_clause->largs);
	fc_rightop = (Node *) linitial(fc_clause->rargs);
	fc_expr_op = linitial_oid(fc_clause->opnos);
	fc_expr_coll = linitial_oid(fc_clause->inputcollids);

	/* 如果相关，排序规则必须匹配 */
	if (!IndexCollMatchesExprColl(fc_idxcollation, fc_expr_coll))
		return NULL;

	/*
	 * 这些语法测试与 match_opclause_to_indexcol() 中的测试相同
	 */
	if (match_index_to_operand(fc_leftop, fc_indexcol, fc_index) &&
		!bms_is_member(fc_index_relid, pull_varnos(fc_root, fc_rightop)) &&
		!contain_volatile_functions(fc_rightop))
	{
		/* 好了，indexkey 在左侧 */
		fc_var_on_left = true;
	}
	else if (match_index_to_operand(fc_rightop, fc_indexcol, fc_index) &&
			 !bms_is_member(fc_index_relid, pull_varnos(fc_root, fc_leftop)) &&
			 !contain_volatile_functions(fc_leftop))
	{
		/* indexkey 在右侧，因此对操作符进行换位 */
		fc_expr_op = get_commutator(fc_expr_op);
		if (fc_expr_op == InvalidOid)
			return NULL;
		fc_var_on_left = false;
	}
	else
		return NULL;

	/* 我们可以，如果运算符是正确类型的 opfamily 成员 */
	switch (get_op_opfamily_strategy(fc_expr_op, fc_opfamily))
	{
		case BTLessStrategyNumber:
		case BTLessEqualStrategyNumber:
		case BTGreaterEqualStrategyNumber:
		case BTGreaterStrategyNumber:
			return fc_expand_indexqual_rowcompare(fc_root,
											   fc_rinfo,
											   fc_indexcol,
											   fc_index,
											   fc_expr_op,
											   fc_var_on_left);
	}

	return NULL;
}

/*
 * expand_indexqual_rowcompare --- 展开单个 indexqual 条件
 *		这是一个 RowCompareExpr
 *
 * 已经知道行比较的第一列与索引的指定列匹配。我们可以将行比较的附加列用作索引资格，只要它们在“相同方向”上与索引匹配，即索引键都在子句的同一侧，并且运算符都是相同类型的 opfamilies 成员。
 *
 * 如果 RowCompareExpr 的所有列都以这种方式匹配，我们就可以原样使用它，除了可能将其变换以使索引键在左侧。
 *
 * 否则，我们将构建一个简化的 RowCompareExpr（如果有多于一列匹配）或一个简单的 OpExpr（如果只有第一列匹配）。在这些情况下，修改后的子句始终是 "<=" 或 ">="，即使原始为 "<" 或 ">" —— 这是为了匹配所有可能与原始匹配的行。（我们在这样做时构建了一个有损版本的行比较，因此我们设置 lossy = true。）
 *
 * 注意：这实际上只是 match_rowcompare_to_indexcol 的后半部分，但我们将其分开以便于理解。
 */
static IndexClause * fc_expand_indexqual_rowcompare(PlannerInfo *fc_root,
							RestrictInfo *fc_rinfo,
							int fc_indexcol,
							IndexOptInfo *fc_index,
							Oid fc_expr_op,
							bool fc_var_on_left)
{
	IndexClause *fc_iclause = makeNode(IndexClause);
	RowCompareExpr *fc_clause = (RowCompareExpr *) fc_rinfo->clause;
	int			fc_op_strategy;
	Oid			fc_op_lefttype;
	Oid			fc_op_righttype;
	int			fc_matching_cols;
	List	   *fc_expr_ops;
	List	   *fc_opfamilies;
	List	   *fc_lefttypes;
	List	   *fc_righttypes;
	List	   *fc_new_ops;
	List	   *fc_var_args;
	List	   *fc_non_var_args;

	fc_iclause->rinfo = fc_rinfo;
	fc_iclause->indexcol = fc_indexcol;

	if (fc_var_on_left)
	{
		fc_var_args = fc_clause->largs;
		fc_non_var_args = fc_clause->rargs;
	}
	else
	{
		fc_var_args = fc_clause->rargs;
		fc_non_var_args = fc_clause->largs;
	}

	get_op_opfamily_properties(fc_expr_op, fc_index->opfamily[fc_indexcol], false,
							   &fc_op_strategy,
							   &fc_op_lefttype,
							   &fc_op_righttype);

	/* 初始化返回的使用了哪些索引列的列表 */
	fc_iclause->indexcols = list_make1_int(fc_indexcol);

	/* 构建运算符、opfamilies 和运算符数据类型的列表，以备需要时使用 */
	fc_expr_ops = list_make1_oid(fc_expr_op);
	fc_opfamilies = list_make1_oid(fc_index->opfamily[fc_indexcol]);
	fc_lefttypes = list_make1_oid(fc_op_lefttype);
	fc_righttypes = list_make1_oid(fc_op_righttype);

	/*
	 * 查看剩余的哪些列以相同方式与某个索引列匹配。就像在 match_clause_to_indexcol() 中一样，只要不会使用来自被索引关系的 Vars，任何潜在索引条件的“其他”侧都可以。
	 */
	fc_matching_cols = 1;

	while (fc_matching_cols < list_length(fc_var_args))
	{
		Node	   *fc_varop = (Node *) list_nth(fc_var_args, fc_matching_cols);
		Node	   *fc_constop = (Node *) list_nth(fc_non_var_args, fc_matching_cols);
		int			fc_i;

		fc_expr_op = list_nth_oid(fc_clause->opnos, fc_matching_cols);
		if (!fc_var_on_left)
		{
			/* indexkey 在右侧，因此对操作符进行换位 */
			fc_expr_op = get_commutator(fc_expr_op);
			if (fc_expr_op == InvalidOid)
				break;			/* 运算符不可用 */
		}
		if (bms_is_member(fc_index->rel->relid, pull_varnos(fc_root, fc_constop)))
			break;				/* 不好，Var 在错误的一侧 */
		if (contain_volatile_functions(fc_constop))
			break;				/* 不好，易变的比较值 */

		/*
		 * Var 侧可以匹配索引的任何键列。
		 */
		for (fc_i = 0; fc_i < fc_index->nkeycolumns; fc_i++)
		{
			if (match_index_to_operand(fc_varop, fc_i, fc_index) &&
				get_op_opfamily_strategy(fc_expr_op,
										 fc_index->opfamily[fc_i]) == fc_op_strategy &&
				IndexCollMatchesExprColl(fc_index->indexcollations[fc_i],
										 list_nth_oid(fc_clause->inputcollids,
													  fc_matching_cols)))
				break;
		}
		if (fc_i >= fc_index->nkeycolumns)
			break;				/* 未找到匹配项 */

		/* 将列号添加到返回列表 */
		fc_iclause->indexcols = lappend_int(fc_iclause->indexcols, fc_i);

		/* 将运算符信息添加到列表 */
		get_op_opfamily_properties(fc_expr_op, fc_index->opfamily[fc_i], false,
								   &fc_op_strategy,
								   &fc_op_lefttype,
								   &fc_op_righttype);
		fc_expr_ops = lappend_oid(fc_expr_ops, fc_expr_op);
		fc_opfamilies = lappend_oid(fc_opfamilies, fc_index->opfamily[fc_i]);
		fc_lefttypes = lappend_oid(fc_lefttypes, fc_op_lefttype);
		fc_righttypes = lappend_oid(fc_righttypes, fc_op_righttype);

		/* 这一列匹配，继续扫描 */
		fc_matching_cols++;
	}

	/* 如果所有列都可用作索引资格，则结果是无损的 */
	fc_iclause->lossy = (fc_matching_cols != list_length(fc_clause->opnos));

	/*
	 * 如果我们在左侧有 var 且它都可用作索引资格，我们可以原样使用 rinfo->clause。
	 */
	if (fc_var_on_left && !fc_iclause->lossy)
		fc_iclause->indexquals = list_make1(fc_rinfo);
	else
	{
		/*
		 * 我们必须生成一个修改过的 rowcompare（可能只是一个 OpExpr）。这一部分的痛苦在于将 < 改为 <= 或 > 改为 >=，所以先处理这个问题。
		 */
		if (!fc_iclause->lossy)
		{
			/* 非常简单，只需使用交换后的运算符 */
			fc_new_ops = fc_expr_ops;
		}
		else if (fc_op_strategy == BTLessEqualStrategyNumber ||
				 fc_op_strategy == BTGreaterEqualStrategyNumber)
		{
			/* 容易，只需使用相同（可能已交换的）运算符 */
			fc_new_ops = list_truncate(fc_expr_ops, fc_matching_cols);
		}
		else
		{
			ListCell   *fc_opfamilies_cell;
			ListCell   *fc_lefttypes_cell;
			ListCell   *fc_righttypes_cell;

			if (fc_op_strategy == BTLessStrategyNumber)
				fc_op_strategy = BTLessEqualStrategyNumber;
			else if (fc_op_strategy == BTGreaterStrategyNumber)
				fc_op_strategy = BTGreaterEqualStrategyNumber;
			else
				elog(ERROR, "unexpected strategy number %d", fc_op_strategy);
			fc_new_ops = NIL;
			forthree(fc_opfamilies_cell, fc_opfamilies,
					 fc_lefttypes_cell, fc_lefttypes,
					 fc_righttypes_cell, fc_righttypes)
			{
				Oid			fc_opfam = lfirst_oid(fc_opfamilies_cell);
				Oid			fc_lefttype = lfirst_oid(fc_lefttypes_cell);
				Oid			fc_righttype = lfirst_oid(fc_righttypes_cell);

				fc_expr_op = get_opfamily_member(fc_opfam, fc_lefttype, fc_righttype,
											  fc_op_strategy);
				if (!OidIsValid(fc_expr_op))	/* 不应该发生 */
					elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
						 fc_op_strategy, fc_lefttype, fc_righttype, fc_opfam);
				fc_new_ops = lappend_oid(fc_new_ops, fc_expr_op);
			}
		}

		/* 如果我们有多列匹配，创建一个子集 rowcompare */
		if (fc_matching_cols > 1)
		{
			RowCompareExpr *fc_rc = makeNode(RowCompareExpr);

			fc_rc->rctype = (RowCompareType) fc_op_strategy;
			fc_rc->opnos = fc_new_ops;
			fc_rc->opfamilies = list_truncate(list_copy(fc_clause->opfamilies),
										   fc_matching_cols);
			fc_rc->inputcollids = list_truncate(list_copy(fc_clause->inputcollids),
											 fc_matching_cols);
			fc_rc->largs = list_truncate(copyObject(fc_var_args),
									  fc_matching_cols);
			fc_rc->rargs = list_truncate(copyObject(fc_non_var_args),
									  fc_matching_cols);
			fc_iclause->indexquals = list_make1(make_simple_restrictinfo(fc_root,
																	  (Expr *) fc_rc));
		}
		else
		{
			Expr	   *fc_op;

			/* 在这种情况下我们不报告索引列列表 */
			fc_iclause->indexcols = NIL;

			fc_op = make_opclause(linitial_oid(fc_new_ops), BOOLOID, false,
							   copyObject(linitial(fc_var_args)),
							   copyObject(linitial(fc_non_var_args)),
							   InvalidOid,
							   linitial_oid(fc_clause->inputcollids));
			fc_iclause->indexquals = list_make1(make_simple_restrictinfo(fc_root, fc_op));
		}
	}

	return fc_iclause;
}


/****************************************************************************
 *				----  检查排序运算符的例程	----
 ****************************************************************************/

/*
 * match_pathkeys_to_index
 *		测试索引是否能产生按照给定 pathkeys 排序的输出，使用“排序运算符”。
 *
 * 如果可以，则返回一个合适的ORDER BY 表达式列表，每个表达式的形式为“indexedcol 运算符 pseudoconstant”，以及一个整型列表，表示每个子句将使用的索引列号（从零开始）。如果无法以这种方式实现排序，则返回 NIL 列表。
 *
 * 成功时，结果列表按 pathkeys 排序，并且实际上与请求的 pathkeys 一一对应。
 */
static void fc_match_pathkeys_to_index(IndexOptInfo *fc_index, List *fc_pathkeys,
						List **fc_orderby_clauses_p,
						List **fc_clause_columns_p)
{
	List	   *fc_orderby_clauses = NIL;
	List	   *fc_clause_columns = NIL;
	ListCell   *fc_lc1;

	*fc_orderby_clauses_p = NIL;	/* 设置默认结果 */
	*fc_clause_columns_p = NIL;

	/* 只有具有 amcanorderbyop 属性的索引在这里才有意义 */
	if (!fc_index->amcanorderbyop)
		return;

	foreach(fc_lc1, fc_pathkeys)
	{
		PathKey    *fc_pathkey = (PathKey *) lfirst(fc_lc1);
		bool		fc_found = false;
		ListCell   *fc_lc2;

		/*
		 * 注意：对于任何匹配失败，我们立即返回 NIL。仅匹配某些 pathkeys 没有价值。
		 */

		/* Pathkey 必须请求目标 opfamily 的默认排序顺序 */
		if (fc_pathkey->pk_strategy != BTLessStrategyNumber ||
			fc_pathkey->pk_nulls_first)
			return;

		/* 如果 eclass 是易变的，则无法使用索引扫描 */
		if (fc_pathkey->pk_eclass->ec_has_volatile)
			return;

		/*
		 * 尝试将 eclass 成员表达式与索引匹配。注意，子 EC 成员被考虑，但仅在它们属于目标关系时。（与常规成员不同，同一表达式可能成为多个 EC 的子成员。因此，同一索引可能被认为匹配多个 pathkey 列表，这在这里是可以的。另请参见 get_eclass_for_sort_expr。）
		 */
		foreach(fc_lc2, fc_pathkey->pk_eclass->ec_members)
		{
			EquivalenceMember *fc_member = (EquivalenceMember *) lfirst(fc_lc2);
			int			fc_indexcol;

			/* 如果引用其他关系，则没有匹配的可能性 */
			if (!bms_equal(fc_member->em_relids, fc_index->rel->relids))
				continue;

			/*
			 * 我们允许索引的任何列与每个路径关键字匹配；它们
			 * 不必从左到右按顺序匹配，这可能是你预期的。这对于 GiST 是正确的，
			 * 而对于 SP-GiST 来说并不重要，因为它根本不处理多列，
			 * 而且没有其他现有的 AM 支持 amcanorderbyop。
			 * 我们可能需要在未来为其他实现提供不同的逻辑。
			 */
			for (fc_indexcol = 0; fc_indexcol < fc_index->nkeycolumns; fc_indexcol++)
			{
				Expr	   *fc_expr;

				fc_expr = fc_match_clause_to_ordering_op(fc_index,
												   fc_indexcol,
												   fc_member->em_expr,
												   fc_pathkey->pk_opfamily);
				if (fc_expr)
				{
					fc_orderby_clauses = lappend(fc_orderby_clauses, fc_expr);
					fc_clause_columns = lappend_int(fc_clause_columns, fc_indexcol);
					fc_found = true;
					break;
				}
			}

			if (fc_found)			/* 不想查看剩余成员 */
				break;
		}

		if (!fc_found)				/* 如果没有匹配此路径关键字则失败 */
			return;
	}

	*fc_orderby_clauses_p = fc_orderby_clauses;	/* 成功！ */
	*fc_clause_columns_p = fc_clause_columns;
}

/*
 * match_clause_to_ordering_op
 *	  确定一个排序操作符表达式是否匹配一个
 *	  索引列。
 *
 *	  这与 match_clause_to_indexcol 相似，但更简单。
 *	  我们只关心简单的 OpExpr 情况。输入是一个
 *	  正在排序的裸表达式，它必须是形式
 *	  (indexkey op const) 或 (const op indexkey)，其中 op 是
 *	  列的 opfamily 的排序操作符。
 *
 * 'index' 是感兴趣的索引。
 * 'indexcol' 是 'index' 的列号（从 0 开始计数）。
 * 'clause' 是要测试的排序表达式。
 * 'pk_opfamily' 是描述所需排序顺序的 btree opfamily。
 *
 * 请注意，我们当前不考虑排序操作符结果的排序规则。
 * 在实际案例中，结果类型将是数字，因此没有排序规则，
 * 如果有排序规则，匹配到什么也不太清楚。
 * 索引的排序规则应该匹配排序操作符的输入排序规则，而不是它的结果。
 *
 * 如果成功，如果 indexkey 在左侧，则原样返回 'clause'，
 * 否则返回 'clause' 的交换副本。如果没有匹配，返回 NULL。
 */
static Expr * fc_match_clause_to_ordering_op(IndexOptInfo *fc_index,
							int fc_indexcol,
							Expr *fc_clause,
							Oid fc_pk_opfamily)
{
	Oid			fc_opfamily;
	Oid			fc_idxcollation;
	Node	   *fc_leftop,
			   *fc_rightop;
	Oid			fc_expr_op;
	Oid			fc_expr_coll;
	Oid			fc_sortfamily;
	bool		fc_commuted;

	Assert(fc_indexcol < fc_index->nkeycolumns);

	fc_opfamily = fc_index->opfamily[fc_indexcol];
	fc_idxcollation = fc_index->indexcollations[fc_indexcol];

	/*
	 * 子句必须是一个二元操作子句。
	 */
	if (!is_opclause(fc_clause))
		return NULL;
	fc_leftop = get_leftop(fc_clause);
	fc_rightop = get_rightop(fc_clause);
	if (!fc_leftop || !fc_rightop)
		return NULL;
	fc_expr_op = ((OpExpr *) fc_clause)->opno;
	fc_expr_coll = ((OpExpr *) fc_clause)->inputcollid;

	/*
	 * 如果排序规则错误，我们可以立即忘记整个事情。
	 */
	if (!IndexCollMatchesExprColl(fc_idxcollation, fc_expr_coll))
		return NULL;

	/*
	 * 检查形式为：(indexkey 操作符 常量) 或
	 * (常量 操作符 indexkey) 的子句。
	 */
	if (match_index_to_operand(fc_leftop, fc_indexcol, fc_index) &&
		!contain_var_clause(fc_rightop) &&
		!contain_volatile_functions(fc_rightop))
	{
		fc_commuted = false;
	}
	else if (match_index_to_operand(fc_rightop, fc_indexcol, fc_index) &&
			 !contain_var_clause(fc_leftop) &&
			 !contain_volatile_functions(fc_leftop))
	{
		/* 可能匹配，但我们需要一个交换操作符 */
		fc_expr_op = get_commutator(fc_expr_op);
		if (fc_expr_op == InvalidOid)
			return NULL;
		fc_commuted = true;
	}
	else
		return NULL;

	/*
	 * (交换的)操作符是否是 opfamily 的排序操作符？如果是，
	 * 它是否产生正确的排序语义？
	 */
	fc_sortfamily = get_op_opfamily_sortfamily(fc_expr_op, fc_opfamily);
	if (fc_sortfamily != fc_pk_opfamily)
		return NULL;

	/* 我们有一个匹配。返回子句或它的交换版本。 */
	if (fc_commuted)
	{
		OpExpr	   *fc_newclause = makeNode(OpExpr);

		/* 扁平化复制子句的所有字段 */
		memcpy(fc_newclause, fc_clause, sizeof(OpExpr));

		/* 交换它 */
		fc_newclause->opno = fc_expr_op;
		fc_newclause->opfuncid = InvalidOid;
		fc_newclause->args = list_make2(fc_rightop, fc_leftop);

		fc_clause = (Expr *) fc_newclause;
	}

	return fc_clause;
}


/****************************************************************************
 *				----  进行部分索引谓词测试的例程	----
 ****************************************************************************/

/*
 * check_index_predicates
 *		为指定关系的每个索引设置由谓词派生的 IndexOptInfo 字段。
 *
 * 如果索引是部分索引，并且它的谓词对于此查询是满足的，
 * 即查询的 WHERE 子句暗示谓词，则 predOK 被设置为 true。
 *
 * indrestrictinfo 被设置为关系的 baserestrictinfo 列表
 * 减去任何由索引的谓词暗示的条件。
 * （显然，对于非部分索引，这和 baserestrictinfo 是相同的。）
 * 在使用索引时，这些条件在某些情况下可以从计划中删除。
 *
 * 一度可以在向 rel 添加更多限制后重新运行这段代码，
 * 从而可能让我们证明更多的索引是 OK 的。
 * 但这不再发生（至少不在核心代码的使用中），
 * 但这段代码仍然支持，以防扩展想要干扰
 * baserestrictinfo 列表。我们假设添加更多限制不会使
 * 索引不再 predOK。每次都必须重新计算 indrestrictinfo，
 * 以确保任何新添加的限制在需要时包含在内。
 */
void check_index_predicates(PlannerInfo *fc_root, RelOptInfo *fc_rel)
{
	List	   *fc_clauselist;
	bool		fc_have_partial;
	bool		fc_is_target_rel;
	Relids		fc_otherrels;
	ListCell   *fc_lc;

	/* 索引仅在基关系或“其他”成员关系上可用。 */
	Assert(IS_SIMPLE_REL(fc_rel));

	/*
	 * 初始化 indrestrictinfo 列表，使其与
	 * baserestrictinfo 完全相同，并检查是否有任何部分索引。
	 * 如果没有，这就是我们需要做的全部。
	 */
	fc_have_partial = false;
	foreach(fc_lc, fc_rel->indexlist)
	{
		IndexOptInfo *fc_index = (IndexOptInfo *) lfirst(fc_lc);

		fc_index->indrestrictinfo = fc_rel->baserestrictinfo;
		if (fc_index->indpred)
			fc_have_partial = true;
	}
	if (!fc_have_partial)
		return;

	/*
	 * 构造一个我们可以假设为真的子句列表，
	 * 用于证明索引可用（即可用的）的目的。
	 * 该关系的限制子句总是可用的，
	 * 任何可以“移动到”该关系的连接子句也是如此。
	 * 此外，我们可以考虑任何 EC 可导出的连接子句
	 * （按定义，它们必须是“可移动到”该关系的）。
	 */
	fc_clauselist = list_copy(fc_rel->baserestrictinfo);

	/* 扫描关系的连接子句 */
	foreach(fc_lc, fc_rel->joininfo)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);

		/* 检查子句是否可以移动到该关系 */
		if (!join_clause_is_movable_to(fc_rinfo, fc_rel))
			continue;

		fc_clauselist = lappend(fc_clauselist, fc_rinfo);
	}

	/*
	 * 添加任何可等价导出的连接子句。
	 * 为 generate_join_implied_equalities 计算正确的 relid 集合稍微有点棘手，
	 * 因为 rel 可能是一个子关系，而不是一个真正的 baserel，
	 * 在这种情况下，我们必须从 all_baserels 中移除其父关系的 relid。
	 */
	if (fc_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
		fc_otherrels = bms_difference(fc_root->all_baserels,
								   find_childrel_parents(fc_root, fc_rel));
	else
		fc_otherrels = bms_difference(fc_root->all_baserels, fc_rel->relids);

	if (!bms_is_empty(fc_otherrels))
		fc_clauselist =
			list_concat(fc_clauselist,
						generate_join_implied_equalities(fc_root,
														 bms_union(fc_rel->relids,
																   fc_otherrels),
														 fc_otherrels,
														 fc_rel));

	
/*
	 * 通常，我们会从 indrestrictinfo 中移除由部分索引的
	 * 断言所暗示的条件，表明这些条件不需要由使用该索引的
	 * indexscan 计划显式检查。然而，如果 rel 是 UPDATE/DELETE/MERGE/SELECT FOR UPDATE 的目标关系，
	 * 我们无法从计划中移除这些条件，因为它们需要在
	 * 计划中，这样才能通过 EvalPlanQual 测试进行正确的重新检查。
	 * 将来我们可能会希望从主计划中移除这些条件，并通过旁通道
	 * 将其传递给 EvalPlanQual；但目前，我们对于目标关系
	 * 完全不移除暗示的条件。
	 */
	fc_is_target_rel = (bms_is_member(fc_rel->relid, fc_root->all_result_relids) ||
					 get_plan_rowmark(fc_root->rowMarks, fc_rel->relid) != NULL);

	/*
	 * 现在尝试证明每个索引谓词为真，并计算部分索引的
	 * indrestrictinfo 列表。请注意，我们甚至对非-predOK 索引也计算
	 * indrestrictinfo 列表；这看起来可能是浪费，但我们可能能够在
	 * OR 子句中使用这些索引，参见 generate_bitmap_or_paths()。
	 */
	foreach(fc_lc, fc_rel->indexlist)
	{
		IndexOptInfo *fc_index = (IndexOptInfo *) lfirst(fc_lc);
		ListCell   *fc_lcr;

		if (fc_index->indpred == NIL)
			continue;			/* 在这里忽略非部分索引 */

		if (!fc_index->predOK)		/* 如果已经证明是 OK，就不要重复工作 */
			fc_index->predOK = predicate_implied_by(fc_index->indpred, fc_clauselist,
												 false);

		/* 如果 rel 是更新目标，保持上述 indrestrictinfo */
		if (fc_is_target_rel)
			continue;

		/* 否则计算非暗示条件的 indrestrictinfo */
		fc_index->indrestrictinfo = NIL;
		foreach(fc_lcr, fc_rel->baserestrictinfo)
		{
			RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lcr);

			/* predicate_implied_by() 假定第一个参数是不可变的 */
			if (contain_mutable_functions((Node *) fc_rinfo->clause) ||
				!predicate_implied_by(list_make1(fc_rinfo->clause),
									  fc_index->indpred, false))
				fc_index->indrestrictinfo = lappend(fc_index->indrestrictinfo, fc_rinfo);
		}
	}
}

/****************************************************************************
 *				----  检查外部可见条件的例程  ----
 ****************************************************************************/

/*
 * ec_member_matches_indexcol
 *	  测试一个等价类成员是否匹配一个索引列。
 *
 * 这是用于 generate_implied_equalities_for_column 的回调。
 */
static bool fc_ec_member_matches_indexcol(PlannerInfo *fc_root, RelOptInfo *fc_rel,
						   EquivalenceClass *fc_ec, EquivalenceMember *fc_em,
						   void *fc_arg)
{
	IndexOptInfo *fc_index = ((ec_member_matches_arg *) fc_arg)->index;
	int			fc_indexcol = ((ec_member_matches_arg *) fc_arg)->indexcol;
	Oid			fc_curFamily;
	Oid			fc_curCollation;

	Assert(fc_indexcol < fc_index->nkeycolumns);

	fc_curFamily = fc_index->opfamily[fc_indexcol];
	fc_curCollation = fc_index->indexcollations[fc_indexcol];

	/*
	 * 如果这是一个 btree 索引，如果其操作族与 EC 不兼容，
	 * 我们可以拒绝它，因为从 EC 生成的任何子句都无法与
	 * 该索引一起使用。对于非-btree 索引，我们无法轻易判断
	 * 从 EC 生成的子句是否可以与索引一起使用，因此
	 * 不检查操作族。这可能意味着我们对一个无用的 EC 返回
	 * "true"，所以我们必须重新检查
	 * generate_implied_equalities_for_column 的结果；见
	 * match_eclass_clauses_to_index。
	 */
	if (fc_index->relam == BTREE_AM_OID &&
		!list_member_oid(fc_ec->ec_opfamilies, fc_curFamily))
		return false;

	/* 不过我们坚持对所有索引类型进行排序匹配 */
	if (!IndexCollMatchesExprColl(fc_curCollation, fc_ec->ec_collation))
		return false;

	return match_index_to_operand((Node *) fc_em->em_expr, fc_indexcol, fc_index);
}

/*
 * relation_has_unique_index_for
 *	  确定关系是否可以证明至多有一行满足
 *	  一组相等条件，因为这些条件约束了某个唯一索引的所有
 *	  列。
 *
 * 这些条件可以通过两种方式之一或两者表示：
 * 1. 一系列 RestrictInfo 节点的列表，调用者已经确定
 * 每个条件是一个可以合并连接的与该关系在一侧的表达式
 * 的相等条件，以及在另一侧的不涉及该关系的表达式。
 * 瞬态的 outer_is_left 标志用于标识我们应该查看哪一侧：
 * 如果 outer_is_left 为 false，则查看左侧；如果为 true，
 * 则查看右侧。
 * 2. 该关系中的一系列表达式及对应的相等运算符列表。
 * 调用者必须已经检查过运算符表示相等。
 * （注意：运算符可能是跨类型的；这些表达式应与其右侧输入
 * 相对应。）
 *
 * 调用者仅需提供来自连接的相等条件；
 * 此例程会自动添加任何可用的 baserestrictinfo 子句。
 * （注意：传入的 restrictlist 将被破坏性修改！）
 */
bool relation_has_unique_index_for(PlannerInfo *fc_root, RelOptInfo *fc_rel,
							  List *fc_restrictlist,
							  List *fc_exprlist, List *fc_oprlist)
{
	ListCell   *fc_ic;

	Assert(list_length(fc_exprlist) == list_length(fc_oprlist));

	/* 短路如果没有索引... */
	if (fc_rel->indexlist == NIL)
		return false;

	/*
	 * 检查关系的限制子句，以寻找可用的 var = const 子句
	 * 我们可以将其添加到 restrictlist 中。
	 */
	foreach(fc_ic, fc_rel->baserestrictinfo)
	{
		RestrictInfo *fc_restrictinfo = (RestrictInfo *) lfirst(fc_ic);

		/*
		 * 注意：can_join 对于限制子句不会被设置，但
		 * mergeopfamilies 会被设置，如果它具有可合并连接的操作符
		 * 并且不包含易失性函数。
		 */
		if (fc_restrictinfo->mergeopfamilies == NIL)
			continue;			/* 不是可合并连接的 */

		/*
		 * 该子句肯定只与给定关系有关。
		 * 如果任一侧是伪常量，则我们可以使用它。
		 */
		if (bms_is_empty(fc_restrictinfo->left_relids))
		{
			/* 右侧为内部 */
			fc_restrictinfo->outer_is_left = true;
		}
		else if (bms_is_empty(fc_restrictinfo->right_relids))
		{
			/* 左侧为内部 */
			fc_restrictinfo->outer_is_left = false;
		}
		else
			continue;

		/* 好的，添加到列表 */
		fc_restrictlist = lappend(fc_restrictlist, fc_restrictinfo);
	}

	/* 短路简单情况 */
	if (fc_restrictlist == NIL && fc_exprlist == NIL)
		return false;

	/* 检查关系的每个索引... */
	foreach(fc_ic, fc_rel->indexlist)
	{
		IndexOptInfo *fc_ind = (IndexOptInfo *) lfirst(fc_ic);
		int			fc_c;

		/*
		 * 如果索引不是唯一的，或者未立即强制执行，或者如果它是
		 * 部分索引，那么在这里就毫无用处。我们无法利用
		 * predOK 部分唯一索引，因为
		 * check_index_predicates() 也会使用连接谓词来
		 * 确定部分索引是否可用。在这里我们需要在评估任何连接之前
		 * 保持的证明。
		 */
		if (!fc_ind->unique || !fc_ind->immediate || fc_ind->indpred != NIL)
			continue;

		/*
		 * 尝试在条件列表中寻找每个索引列。这是
		 * O(N^2) 或更差，但我们希望所有列表都很短。
		 */
		for (fc_c = 0; fc_c < fc_ind->nkeycolumns; fc_c++)
		{
			bool		fc_matched = false;
			ListCell   *fc_lc;
			ListCell   *fc_lc2;

			foreach(fc_lc, fc_restrictlist)
			{
				RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);
				Node	   *fc_rexpr;

				/*
				 * 条件的等式操作符必须是索引操作族的成员，否则它没有为该索引
				 * 声明正确的等式行为。我们首先检查这一点，
				 * 因为这可能比 match_index_to_operand() 更便宜。
				 */
				if (!list_member_oid(fc_rinfo->mergeopfamilies, fc_ind->opfamily[fc_c]))
					continue;

				/*
				 * XXX 在某个时刻我们可能需要在这里检查排序规则。
				 * 目前我们假设所有排序规则都归结为相同的
				 * 等式概念。
				 */

				/* 好的，看看条件操作数是否与索引键匹配 */
				if (fc_rinfo->outer_is_left)
					fc_rexpr = get_rightop(fc_rinfo->clause);
				else
					fc_rexpr = get_leftop(fc_rinfo->clause);

				if (match_index_to_operand(fc_rexpr, fc_c, fc_ind))
				{
					fc_matched = true; /* 列是唯一的 */
					break;
				}
			}

			if (fc_matched)
				continue;

			forboth(fc_lc, fc_exprlist, fc_lc2, fc_oprlist)
			{
				Node	   *fc_expr = (Node *) lfirst(fc_lc);
				Oid			fc_opr = lfirst_oid(fc_lc2);

				/* 查看表达式是否匹配索引键 */
				if (!match_index_to_operand(fc_expr, fc_c, fc_ind))
					continue;

				/*
				 * 等式操作符必须是索引
				 * 操作族的成员，否则它没有为该索引
				 * 声明正确的等式行为。我们假设调用者
				 * 确定它是一个等式操作符，所以我们不需要
				 * 更严格地检查。
				 */
				if (!op_in_opfamily(fc_opr, fc_ind->opfamily[fc_c]))
					continue;

				/*
				 * XXX 在某个时刻我们可能需要在这里检查排序规则。
				 * 目前我们假设所有排序规则都归结为相同的
				 * 等式概念。
				 */

				fc_matched = true; /* 列是唯一的 */
				break;
			}

			if (!fc_matched)
				break;			/* 没有匹配；此索引对我们没有帮助 */
		}

		/* 匹配了此索引的所有键列吗？ */
		if (fc_c == fc_ind->nkeycolumns)
			return true;
	}

	return false;
}

/*
 * indexcol_is_bool_constant_for_query
 *
 * 如果索引列被查询的 WHERE 条件约束为常量值，
 * 那么它与排序顺序的考虑无关。
 * 通常这意味着我们有一个限制子句 WHERE indexcol = constant，
 * 这被转化为一个包含常量的等价类，build_index_pathkeys() 会将其识别为冗余。
 * 但是如果索引列是布尔变量（或表达式），那么我们将不会看到
 * WHERE indexcol = constant，因为表达式预处理会将其简化为 "WHERE indexcol"
 * 或 "WHERE NOT indexcol"。所以我们不会有匹配的等价类
 * （除非查询还包含 "ORDER BY indexcol"）。为了允许这样的情况与非布尔值
 * 的工作方式相同，提供了此函数来检测指定的索引列是否匹配布尔限制子句。
 */
bool indexcol_is_bool_constant_for_query(PlannerInfo *fc_root,
									IndexOptInfo *fc_index,
									int fc_indexcol)
{
	ListCell   *fc_lc;

	/* 如果索引不是布尔值，我们不可能得到匹配 */
	if (!IsBooleanOpfamily(fc_index->opfamily[fc_indexcol]))
		return false;

	/* 检查索引的关系的每个限制子句 */
	foreach(fc_lc, fc_index->rel->baserestrictinfo)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);

		/*
		 * 在 match_clause_to_indexcol 中，绝不要将伪常量匹配到
		 * 索引。（在这里这样做在语义上可能是可以的，但得到匹配的机会微乎其微，因此不要浪费处理周期。）
		 */
		if (fc_rinfo->pseudoconstant)
			continue;

		/* 查看我们是否可以将子句的表达式与索引列匹配 */
		if (fc_match_boolean_index_clause(fc_root, fc_rinfo, fc_indexcol, fc_index))
			return true;
	}

	return false;
}


/****************************************************************************
 *				----  检查操作数的例程  ----
 ****************************************************************************/

/*
 * match_index_to_operand()
 *	  一般化测试以检查索引的键
 *	  和限制或连接子句一侧的操作数之间的匹配。
 *
 * operand: 待与索引比较的节点树
 * indexcol: 索引的列号（从 0 开始计数）
 * index: 感兴趣的索引
 *
 * 请注意，我们对此不感兴趣；调用者必须检查
 * 是否存在排序规则匹配，如果它处理的是相关的操作符。
 *
 * 这是为在 selfuncs.c 中使用而导出的。
 */
bool match_index_to_operand(Node *fc_operand,
					   int fc_indexcol,
					   IndexOptInfo *fc_index)
{
	int			fc_indkey;

	/*
	 * 忽略操作数上方的任何 RelabelType 节点。这是为了能够
	 * 在二进制兼容操作符情况下应用索引扫描。注意：
	 * 我们可以假设最多只有一个 RelabelType 节点；
	 * eval_const_expressions() 如果有多个将会进行简化。
	 */
	if (fc_operand && IsA(fc_operand, RelabelType))
		fc_operand = (Node *) ((RelabelType *) fc_operand)->arg;

	fc_indkey = fc_index->indexkeys[fc_indexcol];
	if (fc_indkey != 0)
	{
		/*
		 * 简单索引列；操作数必须是匹配的 Var。
		 */
		if (fc_operand && IsA(fc_operand, Var) &&
			fc_index->rel->relid == ((Var *) fc_operand)->varno &&
			fc_indkey == ((Var *) fc_operand)->varattno)
			return true;
	}
	else
	{
		
/*
		 * 索引表达式；找到正确的表达式。 （这个搜索可以避免，代价是使这个例程的所有调用者更加复杂；似乎不值得。）
		 */
		ListCell   *fc_indexpr_item;
		int			fc_i;
		Node	   *fc_indexkey;

		fc_indexpr_item = list_head(fc_index->indexprs);
		for (fc_i = 0; fc_i < fc_indexcol; fc_i++)
		{
			if (fc_index->indexkeys[fc_i] == 0)
			{
				if (fc_indexpr_item == NULL)
					elog(ERROR, "wrong number of index expressions");
				fc_indexpr_item = lnext(fc_index->indexprs, fc_indexpr_item);
			}
		}
		if (fc_indexpr_item == NULL)
			elog(ERROR, "wrong number of index expressions");
		fc_indexkey = (Node *) lfirst(fc_indexpr_item);

		/*
		 * 它是否匹配操作数？ 再次，去掉任何重新标记。
		 */
		if (fc_indexkey && IsA(fc_indexkey, RelabelType))
			fc_indexkey = (Node *) ((RelabelType *) fc_indexkey)->arg;

		if (equal(fc_indexkey, fc_operand))
			return true;
	}

	return false;
}

/*
 * is_pseudo_constant_for_index()
 *	  测试给定表达式是否可以用作索引扫描的比较值。
 *
 * 索引扫描比较值不得包含任何易变函数，并且不能包含与索引自身表的任何变量。 其他表的变量是可以的；在这种情况下，我们将生成可用于参数化索引扫描的 indexqual。因此，这比 is_pseudo_constant_clause() 的条件要弱。
 *
 * 此函数为计划支持函数导出，这些函数将能够访问 IndexOptInfo，但没有任何 RestrictInfo 基础设施。它进行的测试与上面诸如 match_opclause_to_indexcol() 的函数所做的相同，但这些函数在可能的情况下依赖于 RestrictInfo 关于变量成员资格的信息。
 *
 * expr: 要检查的节点树
 * index: 关注的索引
 */
bool is_pseudo_constant_for_index(PlannerInfo *fc_root, Node *fc_expr, IndexOptInfo *fc_index)
{
	/* pull_varnos 比易变性检查更便宜，因此先执行此操作 */
	if (bms_is_member(fc_index->rel->relid, pull_varnos(fc_root, fc_expr)))
		return false;			/* 不好，包含表的变量 */
	if (contain_volatile_functions(fc_expr))
		return false;			/* 不好，易变的比较值 */
	return true;
}
